/* Programs.jsx -- Programs page redesign per CR #140.
   Map is no longer the centerpiece. Programs are grouped by category and
   shown as colorful editorial cards. Clicking a card opens a modal with a
   small Leaflet map zoomed to that program's location, plus the description
   and the rec center's contact info. */

const CATEGORIES = [
  { id: "Dance",   label: "Dance",   color: "#D8552B", chipClass: "dance" },
  { id: "Theater", label: "Theater", color: "#C28215", chipClass: "musical" },
  { id: "Music",   label: "Music",   color: "#7A8B5C", chipClass: "singing" },
];

function colorFor(type) {
  const m = CATEGORIES.find(c => c.id === type);
  return m ? m.color : "#2A1820";
}

// DB rows from /api/programs have different field names + numeric lat/lng;
// normalize so cards/modal don't need to know about both shapes.
function normalizeDbProgram(row) {
  const lat = row.lat != null ? Number(row.lat) : null;
  const lng = row.lng != null ? Number(row.lng) : null;
  return {
    id:          row.slug ? `db-${row.slug}` : `db-${row.id}`,
    name:        row.name,
    type:        row.type,
    center:      row.center_name,
    region:      row.region || "Philadelphia",
    coords:      (lat != null && lng != null) ? [lat, lng] : null,
    day:         row.day || row.time_text || "",
    phone:       row.contact_phone || "",
    email:       row.contact_email || "",
    description: row.description || "",
    address:     row.address || "",
  };
}

function ProgramsPage() {
  const [filter, setFilter] = React.useState("all");
  const [regionFilter, setRegionFilter] = React.useState("all");
  const [selected, setSelected] = React.useState(null);
  // CR #228: one-time "under construction" notice. Persists ack in
  // sessionStorage so the modal only fires on the first programs-page
  // visit per browser session.
  const [showDemoNotice, setShowDemoNotice] = React.useState(() => {
    try { return sessionStorage.getItem("ppr_programs_demo_ack") !== "1"; }
    catch (_) { return true; }
  });
  const ackDemoNotice = () => {
    try { sessionStorage.setItem("ppr_programs_demo_ack", "1"); } catch (_) {}
    setShowDemoNotice(false);
  };
  // DB-side approved programs (from /api/programs). Merged with the seed
  // PROGRAMS in data.js so the original 10 always show even before the
  // first center submission, and newly-approved center submissions appear
  // here without a code change.
  const [dbPrograms, setDbPrograms] = React.useState([]);

  React.useEffect(() => {
    let cancelled = false;
    fetch("/api/programs", { headers: { accept: "application/json" } })
      .then(r => r.ok ? r.json() : { ok: false, rows: [] })
      .then(j => {
        if (cancelled) return;
        const rows = Array.isArray(j.rows) ? j.rows : [];
        setDbPrograms(rows.map(normalizeDbProgram));
      })
      .catch(() => { /* fall back silently to seed-only */ });
    return () => { cancelled = true; };
  }, []);

  // Seed first (stable order, original 10), then any newly-approved
  // submissions appended.
  const programs = [...(window.PROGRAMS || []), ...dbPrograms];

  // Region chips are derived from the data so newly-approved center
  // submissions in a previously-unseen region don't require a code
  // change to surface in the filter row.
  const allRegions = React.useMemo(() => {
    const set = new Set();
    programs.forEach(p => { if (p.region) set.add(p.region); });
    return Array.from(set).sort();
  }, [programs]);

  const filtered = programs.filter(p =>
    (filter === "all" || p.type === filter) &&
    (regionFilter === "all" || p.region === regionFilter)
  );

  const grouped = CATEGORIES.map(cat => ({
    ...cat,
    items: filtered.filter(p => p.type === cat.id)
  })).filter(g => g.items.length > 0);

  const selectedProgram = selected ? programs.find(p => p.id === selected) : null;

  return (
    <div>
      {showDemoNotice && (
        <div
          role="dialog"
          aria-modal="true"
          aria-labelledby="demo-notice-title"
          style={{
            position: "fixed", inset: 0, zIndex: 200,
            background: "rgba(42,24,32,0.55)",
            display: "flex", alignItems: "center", justifyContent: "center",
            padding: 24,
          }}
        >
          <div
            className="card"
            style={{
              background: "var(--c-paper)",
              border: "2px solid var(--c-ink)",
              borderRadius: 18,
              padding: 28,
              maxWidth: 520,
              boxShadow: "var(--shadow-lift)",
            }}
          >
            <div className="eyebrow" style={{ color: "var(--c-primary)" }}>Under construction</div>
            <div className="display" style={{ fontSize: 26, marginTop: 6, lineHeight: 1.1 }}>
              These are demo programs.
            </div>
            <p style={{ fontSize: 14.5, color: "var(--c-ink)", lineHeight: 1.55, marginTop: 12 }}>
              The programs shown below are placeholders while we collect
              current program information from rec centers across the city.
              Real listings will appear here as soon as centers submit their
              schedules. Thanks for your patience.
            </p>
            <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 20 }}>
              <button type="button" className="btn ochre" onClick={ackDemoNotice} autoFocus>
                Continue &rarr;
              </button>
            </div>
          </div>
        </div>
      )}

      <section style={{ background: "var(--c-cream)", padding: "72px 0 32px" }}>
        <div className="container">
          <div className="eyebrow">Year-round Programs</div>
          <h1 className="display" style={{ fontSize: "clamp(48px, 8vw, 108px)", margin: "12px 0 18px", lineHeight: 0.95 }}>
            Find a program<br />near home.
          </h1>
          <p style={{ fontSize: 18, maxWidth: 640, color: "var(--c-ink-soft)", lineHeight: 1.5 }}>
            Low-cost rec center programs in dance, theater, and music.
            Tap a program to see the location, schedule, and how to sign up.
          </p>
        </div>
      </section>

      <section className="section-tight">
        <div className="container">
          <div className="pgm-filter-stack">
            <div className="pgm-filter-row">
              <div className="pgm-filter-label">Type</div>
              <div className="pgm-filter-chips">
                <FilterChip
                  active={filter === "all"} onClick={() => setFilter("all")}
                  color="var(--c-ink)" label="All" count={programs.length}
                />
                {CATEGORIES.map(c => (
                  <FilterChip
                    key={c.id}
                    active={filter === c.id} onClick={() => setFilter(c.id)}
                    color={c.color} label={c.label}
                    count={programs.filter(p => p.type === c.id).length}
                  />
                ))}
              </div>
            </div>
            {allRegions.length > 1 && (
              <div className="pgm-filter-row">
                <div className="pgm-filter-label">Region</div>
                <div className="pgm-filter-chips">
                  <FilterChip
                    active={regionFilter === "all"} onClick={() => setRegionFilter("all")}
                    color="var(--c-ink)" label="All" count={programs.length}
                  />
                  {allRegions.map(r => (
                    <FilterChip
                      key={r}
                      active={regionFilter === r} onClick={() => setRegionFilter(r)}
                      color="var(--c-ink)" label={r}
                      count={programs.filter(p => p.region === r).length}
                    />
                  ))}
                </div>
              </div>
            )}
          </div>

          {grouped.map(group => (
            <CategorySection key={group.id} group={group} onSelect={setSelected} />
          ))}

          {grouped.length === 0 && (
            <div style={{ textAlign: "center", padding: "48px 0", color: "var(--c-ink-soft)", fontFamily: "var(--f-mono)", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>
              No programs in this category yet.
            </div>
          )}
        </div>
      </section>

      {selectedProgram && (
        <ProgramModal program={selectedProgram} onClose={() => setSelected(null)} />
      )}

      <style>{`
        .programs-grid {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 18px;
        }
        @media (max-width: 980px) { .programs-grid { grid-template-columns: 1fr 1fr; } }
        @media (max-width: 640px) { .programs-grid { grid-template-columns: 1fr; } }

        .pgm-card {
          /* CR #281: pin the button's text color so Safari/iOS doesn't
             render the unstyled <button> in -apple-system-blue. The card
             title (.pgm-name) inherits this. */
          color: var(--c-ink);
          border: 2px solid var(--c-ink);
          border-radius: 18px;
          background: var(--c-paper);
          padding: 22px 22px 20px;
          cursor: pointer;
          text-align: left;
          width: 100%;
          font: inherit;
          box-shadow: 4px 4px 0 var(--c-ink);
          transition: transform 140ms ease, box-shadow 140ms ease;
          display: flex; flex-direction: column; gap: 10px;
          position: relative;
          overflow: hidden;
        }
        .pgm-card:hover, .pgm-card:focus-visible {
          transform: translate(-3px, -3px);
          box-shadow: 7px 7px 0 var(--c-ink);
          outline: none;
        }
        .pgm-card::before {
          content: "";
          position: absolute; top: 0; left: 0; right: 0; height: 6px;
          background: var(--pgm-accent, var(--c-primary));
        }
        .pgm-card .pgm-name {
          font-family: var(--f-display);
          font-weight: 700;
          font-size: 22px;
          line-height: 1.1;
          margin-top: 4px;
        }
        .pgm-card .pgm-meta {
          font-family: var(--f-mono);
          font-size: 10.5px;
          letter-spacing: 0.1em;
          text-transform: uppercase;
          color: var(--c-ink-soft);
          margin-top: auto;
        }
        .pgm-card .pgm-hint {
          font-family: var(--f-mono);
          font-size: 10px;
          letter-spacing: 0.12em;
          text-transform: uppercase;
          color: var(--pgm-accent);
          margin-top: 6px;
          display: flex; align-items: center; gap: 6px;
        }

        .pgm-section-head {
          display: flex; align-items: baseline; gap: 14px;
          margin: 28px 0 18px;
          padding-bottom: 10px;
          border-bottom: 2px solid var(--c-ink);
        }
        .pgm-section-head .pgm-section-title {
          font-family: var(--f-display);
          font-weight: 800;
          font-size: clamp(28px, 4vw, 40px);
          line-height: 1;
        }
        .pgm-section-head .pgm-section-count {
          font-family: var(--f-mono);
          font-size: 11px;
          letter-spacing: 0.14em;
          text-transform: uppercase;
          color: var(--c-ink-soft);
        }
        .pgm-section-head::before {
          content: "";
          width: 14px; height: 14px;
          border-radius: 50%;
          background: var(--pgm-accent);
          border: 2px solid var(--c-ink);
          align-self: center;
        }

        .pgm-filter-chip {
          display: inline-flex; align-items: center; gap: 6px;
          padding: 10px 16px; min-height: 44px;
          border: 2px solid var(--c-ink);
          border-radius: 999px;
          background: var(--c-paper);
          color: var(--c-ink);
          font-family: var(--f-display);
          font-weight: 600; font-size: 14px;
          cursor: pointer;
          transition: background 120ms;
        }
        .pgm-filter-chip:hover { background: var(--c-cream-deep); }
        .pgm-filter-chip.is-active { background: var(--chip-color); color: var(--c-paper); border-color: var(--chip-color); }
        .pgm-filter-chip .pgm-filter-count {
          font-family: var(--f-mono); font-size: 10.5px; opacity: 0.75;
        }

        /* Two-row filter stack: Type chips on top, Region chips below.
           Each row has a small uppercase label so it's obvious which
           dimension you're filtering. The region row is data-driven so
           a newly-approved program in a previously-unseen region adds
           a chip without a code change. */
        .pgm-filter-stack {
          display: flex; flex-direction: column; gap: 14px;
          margin-bottom: 36px;
        }
        .pgm-filter-row {
          display: grid;
          grid-template-columns: 88px 1fr;
          gap: 12px;
          align-items: center;
        }
        .pgm-filter-label {
          font-family: var(--f-mono);
          font-size: 10.5px;
          letter-spacing: 0.14em;
          text-transform: uppercase;
          color: var(--c-ink-soft);
          align-self: center;
        }
        .pgm-filter-chips {
          display: flex; gap: 8px; flex-wrap: wrap;
        }
        @media (max-width: 640px) {
          .pgm-filter-row { grid-template-columns: 1fr; gap: 6px; }
          .pgm-filter-chips {
            flex-wrap: nowrap;
            overflow-x: auto;
            -webkit-overflow-scrolling: touch;
            margin: 0 -16px;
            padding: 4px 16px;
            scroll-snap-type: x proximity;
          }
          .pgm-filter-chips::-webkit-scrollbar { display: none; }
          .pgm-filter-chip { flex-shrink: 0; scroll-snap-align: start; }
        }
      `}</style>
    </div>
  );
}

function FilterChip({ active, onClick, color, label, count }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={`pgm-filter-chip ${active ? "is-active" : ""}`}
      style={{ "--chip-color": color }}
    >
      {label}
      <span className="pgm-filter-count">{count}</span>
    </button>
  );
}

function CategorySection({ group, onSelect }) {
  return (
    <div style={{ "--pgm-accent": group.color }}>
      <div className="pgm-section-head">
        <div className="pgm-section-title">{group.label}</div>
        <div className="pgm-section-count">{group.items.length} programs</div>
      </div>
      <div className="programs-grid">
        {group.items.map(p => (
          <ProgramCard key={p.id} program={p} accent={group.color} onClick={() => onSelect(p.id)} />
        ))}
      </div>
    </div>
  );
}

function ProgramCard({ program, accent, onClick }) {
  return (
    <button type="button" className="pgm-card" onClick={onClick} style={{ "--pgm-accent": accent }}>
      <span className={`chip ${categoryChipClass(program.type)}`} style={{ alignSelf: "flex-start" }}>
        {program.type}
      </span>
      <div className="pgm-name">{program.name}</div>
      <div className="pgm-meta">
        {program.center} &middot; {program.day}
      </div>
      <div className="pgm-hint">
        Details &amp; location <span aria-hidden="true">&rarr;</span>
      </div>
    </button>
  );
}

function categoryChipClass(type) {
  if (type === "Dance") return "dance";
  if (type === "Theater") return "musical";
  if (type === "Music") return "singing";
  return "";
}

// Build a Google Maps directions URL. Prefer coordinates when available
// (most precise), fall back to the textual address. Per Google Maps URL
// schema: https://developers.google.com/maps/documentation/urls/get-started
function directionsUrl(p) {
  let dest;
  if (p.coords && p.coords.length === 2) {
    dest = `${p.coords[0]},${p.coords[1]}`;
  } else if (p.address) {
    dest = p.address;
  } else if (p.center) {
    dest = `${p.center} Philadelphia PA`;
  } else {
    dest = "";
  }
  return `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(dest)}`;
}

/* ---------------- MODAL ---------------- */

function ProgramModal({ program, onClose }) {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const accent = colorFor(program.type);

  // Lock body scroll while modal is open + esc-to-close.
  React.useEffect(() => {
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = "";
      window.removeEventListener("keydown", onKey);
    };
  }, [onClose]);

  // Init Leaflet only when the modal is mounted -- keeps the page-load
  // weight off the main programs view.
  React.useEffect(() => {
    if (!program.coords || !containerRef.current) return;
    let cancelled = false;

    function init() {
      if (cancelled || !window.L || !containerRef.current || mapRef.current) return;
      const L = window.L;
      const map = L.map(containerRef.current, {
        center: program.coords,
        zoom: 15,
        zoomControl: false,
        attributionControl: false,
        scrollWheelZoom: false,
        dragging: true,
        doubleClickZoom: false,
      });
      L.tileLayer("https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png", {
        subdomains: "abcd",
        maxZoom: 19,
      }).addTo(map);
      const icon = L.divIcon({
        className: "pgm-modal-pin",
        html: `<div class="pgm-modal-pin-body" style="background:${accent}"><div class="pgm-modal-pin-dot"></div></div>`,
        iconSize: [22, 22],
        iconAnchor: [11, 11],
      });
      L.marker(program.coords, { icon, interactive: false }).addTo(map);
      mapRef.current = map;
    }

    if (window.L) init();
    else {
      const interval = setInterval(() => { if (window.L) { clearInterval(interval); init(); } }, 50);
      return () => { cancelled = true; clearInterval(interval); if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; } };
    }
    return () => {
      cancelled = true;
      if (mapRef.current) { mapRef.current.remove(); mapRef.current = null; }
    };
  }, [program.coords, accent]);

  const phoneHref = program.phone ? `tel:${program.phone.replace(/[^0-9+]/g, "")}` : null;
  const emailHref = program.email ? `mailto:${program.email}` : null;

  return (
    <div
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, background: "rgba(42,24,32,0.7)", zIndex: 100,
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 20, overflowY: "auto",
      }}
      role="dialog"
      aria-modal="true"
      aria-label={program.name}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="card"
        style={{ maxWidth: 640, width: "100%", padding: 0, background: "var(--c-paper)", maxHeight: "92vh", display: "flex", flexDirection: "column", overflow: "hidden" }}
      >
        <div style={{ padding: "18px 22px", borderBottom: "2px solid var(--c-ink)", background: "var(--c-cream-deep)", display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
          <div>
            <span className={`chip ${categoryChipClass(program.type)}`}>{program.type}</span>
            <div className="display" style={{ fontSize: 24, marginTop: 8, lineHeight: 1.05 }}>{program.name}</div>
            <div style={{ fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--c-ink-soft)", marginTop: 6 }}>
              {program.center} &middot; {program.day}
            </div>
          </div>
          <button
            onClick={onClose}
            aria-label="Close"
            style={{
              background: "var(--c-paper)", border: "2px solid var(--c-ink)", borderRadius: 999,
              width: 36, height: 36, fontSize: 18, fontWeight: 800, cursor: "pointer",
              flexShrink: 0,
            }}
          >&times;</button>
        </div>

        <div style={{ overflowY: "auto", padding: 22, display: "flex", flexDirection: "column", gap: 18 }}>
          {program.description && (
            <p style={{ fontSize: 15.5, lineHeight: 1.6, color: "var(--c-ink)", margin: 0 }}>
              {program.description}
            </p>
          )}

          {program.coords ? (
            <div
              ref={containerRef}
              style={{ height: 220, width: "100%", border: "2px solid var(--c-ink)", borderRadius: 14, overflow: "hidden", background: "var(--c-cream-deep)" }}
              aria-label={`Map of ${program.center}`}
            />
          ) : program.address ? (
            <div style={{ padding: 14, border: "2px solid var(--c-ink)", borderRadius: 14, background: "var(--c-cream-deep)", fontFamily: "var(--f-mono)", fontSize: 12, letterSpacing: "0.06em", color: "var(--c-ink-soft)" }}>
              {program.address}
            </div>
          ) : null}

          <div className="pgm-modal-info">
            <div>
              <div className="eyebrow" style={{ marginBottom: 4 }}>Center</div>
              <div style={{ fontFamily: "var(--f-display)", fontWeight: 600, fontSize: 16 }}>{program.center}</div>
              {program.region && (
                <div style={{ fontSize: 13, color: "var(--c-ink-soft)" }}>{program.region}</div>
              )}
              {program.address && (
                <div style={{ fontSize: 13, color: "var(--c-ink-soft)", marginTop: 4 }}>{program.address}</div>
              )}
            </div>
            <div>
              <div className="eyebrow" style={{ marginBottom: 4 }}>For info, contact</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {phoneHref && (
                  <a
                    href={phoneHref}
                    className="pgm-modal-link"
                    style={{
                      fontFamily: "var(--f-display)", fontWeight: 700, fontSize: 18,
                      "--pgm-link-accent": accent,
                    }}
                  >
                    {program.phone}
                  </a>
                )}
                {emailHref && (
                  <a
                    href={emailHref}
                    className="pgm-modal-link"
                    style={{
                      fontFamily: "var(--f-display)", fontWeight: 600, fontSize: 14.5,
                      wordBreak: "break-all",
                      "--pgm-link-accent": accent,
                    }}
                  >
                    {program.email}
                  </a>
                )}
                {!phoneHref && !emailHref && (
                  <div style={{ fontFamily: "var(--f-mono)", fontSize: 13, color: "var(--c-ink-soft)" }}>
                    Call PPR: 215-685-3585
                  </div>
                )}
              </div>
            </div>
          </div>

          {(program.coords || program.address) && (
            <a
              href={directionsUrl(program)}
              target="_blank"
              rel="noopener noreferrer"
              className="pgm-directions-btn"
              style={{ "--btn-accent": accent }}
            >
              Get directions <span aria-hidden="true">&rarr;</span>
            </a>
          )}
        </div>
      </div>

      <style>{`
        .pgm-modal-pin { position: relative; }
        .pgm-modal-pin-body {
          width: 22px; height: 22px;
          border-radius: 50%;
          border: 2px solid var(--c-ink);
          box-shadow: 1px 1px 0 var(--c-ink);
          display: flex; align-items: center; justify-content: center;
        }
        .pgm-modal-pin-dot {
          width: 6px; height: 6px;
          background: var(--c-paper);
          border-radius: 50%;
        }

        .pgm-modal-info {
          display: grid;
          grid-template-columns: 1fr 1fr;
          gap: 14px;
        }
        @media (max-width: 540px) {
          .pgm-modal-info { grid-template-columns: 1fr; }
        }

        /* CR #278: phone + email links inside the program modal.
           Browser defaults paint :visited tel:/mailto: anchors blue/purple,
           which JJ flagged. We pin every state to ink (effectively black on
           the default palette) and add a clear hover affordance so users
           still know these are clickable. */
        .pgm-modal-link,
        .pgm-modal-link:link,
        .pgm-modal-link:visited,
        .pgm-modal-link:active {
          color: var(--c-ink);
          text-decoration: underline;
          text-decoration-color: var(--pgm-link-accent, var(--c-ink));
          text-decoration-thickness: 2px;
          text-underline-offset: 4px;
          transition: color 120ms ease, text-decoration-thickness 120ms ease;
        }
        .pgm-modal-link:hover,
        .pgm-modal-link:focus-visible {
          color: var(--pgm-link-accent, var(--c-ink));
          text-decoration-thickness: 3px;
          outline: none;
        }

        /* Directions CTA. Bigger, accent-colored, full-width on mobile so
           it's a clear primary action on the modal. */
        .pgm-directions-btn {
          display: inline-flex; align-items: center; justify-content: center;
          gap: 8px;
          padding: 14px 22px; min-height: 48px;
          background: var(--btn-accent, var(--c-ink));
          color: var(--c-paper);
          border: 2px solid var(--c-ink);
          border-radius: 999px;
          box-shadow: 3px 3px 0 var(--c-ink);
          font-family: var(--f-display);
          font-weight: 700; font-size: 15px;
          text-decoration: none;
          transition: transform 120ms ease, box-shadow 120ms ease;
          align-self: flex-start;
        }
        .pgm-directions-btn:hover {
          transform: translate(-2px, -2px);
          box-shadow: 5px 5px 0 var(--c-ink);
        }
        @media (max-width: 540px) {
          .pgm-directions-btn { width: 100%; align-self: stretch; }
        }
      `}</style>
    </div>
  );
}

Object.assign(window, { ProgramsPage });
