/* ============================================================
   calendar-components.jsx — IntroNote, Tile, SongModal, PreviewBar
   ============================================================ */
const { useState, useEffect, useRef } = React;
const IS_AUTHOR = !!(window.omelette && window.omelette.writeFile);
Object.assign(window, { IS_AUTHOR });

/* ---------- password gate ---------- */
function Gate({ onUnlock }) {
  const [value, setValue] = useState("");
  const [error, setError] = useState(false);
  const submit = (e) => {
    e.preventDefault();
    const v = value.trim().toLowerCase();
    if (v === "goodmorning") onUnlock(true);
    else if (v === "withlove") onUnlock(false);
    else { setError(true); setValue(""); }
  };
  return (
    <div className="gate">
      <form className="gate-card" onSubmit={submit}>
        <Cassette size={56} />
        <h1 className="hand gate-title">a mixtape, just for you</h1>
        <input
          className="gate-input" type="password" autoFocus
          value={value} placeholder="password"
          onChange={(e) => { setValue(e.target.value); setError(false); }}
        />
        {error && <p className="gate-error">that's not it — try again</p>}
        <button className="gate-btn" type="submit">unlock</button>
      </form>
    </div>
  );
}

/* ---------- tiny cassette mark (simple shapes only) ---------- */
function Cassette({ size = 54 }) {
  return (
    <svg width={size} height={size * 0.66} viewBox="0 0 100 66" aria-hidden="true">
      <rect x="2" y="2" width="96" height="62" rx="7" fill="var(--olive)" />
      <rect x="9" y="9" width="82" height="34" rx="4" fill="var(--paper)" />
      <circle cx="34" cy="26" r="9" fill="none" stroke="var(--olive-deep)" strokeWidth="3" />
      <circle cx="66" cy="26" r="9" fill="none" stroke="var(--blush-deep)" strokeWidth="3" />
      <circle cx="34" cy="26" r="2.4" fill="var(--olive-deep)" />
      <circle cx="66" cy="26" r="2.4" fill="var(--blush-deep)" />
      <rect x="30" y="23.5" width="40" height="5" rx="2.5" fill="oklch(0.27 0.018 110 / 0.10)" />
      <rect x="20" y="50" width="60" height="8" rx="3" fill="var(--paper)" opacity="0.9" />
      <circle cx="26" cy="54" r="2.4" fill="var(--olive-deep)" />
      <circle cx="74" cy="54" r="2.4" fill="var(--olive-deep)" />
    </svg>
  );
}

/* ---------- editable + persisted text ---------- */
function Editable({ value, onChange, className, style, as = "div", placeholder }) {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current && ref.current.innerText !== value) ref.current.innerText = value;
  }, [value]);
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={className}
      style={style}
      contentEditable={IS_AUTHOR}
      suppressContentEditableWarning
      spellCheck={false}
      data-placeholder={placeholder}
      onBlur={(e) => IS_AUTHOR && onChange(e.currentTarget.innerText)}
    />
  );
}

/* ---------- intro note ---------- */
function IntroNote({ title, setTitle, note, setNote, unlocked, opened }) {
  return (
    <header className="intro">
      <div className="intro-mark">
        <Cassette size={62} />
        <div className="intro-kicker"><span>DO NOT SHUFFLE</span><span className="dot"> · </span><span>ANNIVERSARY EDITION</span></div>
      </div>

      <div className="intro-body">
        <Editable as="h1" className="display intro-title" value={title} onChange={setTitle}
                  placeholder="A title for the year…" />
        <Editable className="hand intro-hand" value={note} onChange={setNote}
                  placeholder="Write your little note here…" />
        <div className="intro-meta">
          <span><b>{opened}</b> played</span>
          <span className="dot">·</span>
          <span><b>{unlocked}</b> unlocked</span>
          <span className="dot">·</span>
          <span><b>{window.TOTAL_DAYS - unlocked}</b> still wrapped</span>
        </div>
      </div>

      <figure className="polaroid">
        <image-slot id="intro-polaroid" shape="rect"
          style={{ width: "100%", height: "168px", display: "block" }}
          placeholder="drop a photo of us"></image-slot>
        <figcaption className="hand">hello!!!</figcaption>
      </figure>
    </header>
  );
}

/* ---------- a single day tile — a "window" onto the month's shared picture ---------- */
function Tile({ d, status, onOpen, currentDay }) {
  if (d.filler) {
    return <span className="tile tile-filler" style={{ gridColumn: d.gridColumn, gridRow: d.gridRow }} aria-hidden="true" />;
  }
  const locked = status === "locked";
  const [shake, setShake] = useState(false);

  const handleClick = () => {
    if (locked) { setShake(true); setTimeout(() => setShake(false), 420); return; }
    onOpen(d);
  };

  return (
    <button
      type="button"
      onClick={handleClick}
      className={`tile tile-${status} tint-${d.tint} ${shake ? "is-shake" : ""}`}
      style={{ gridColumn: d.gridColumn, gridRow: d.gridRow }}
      aria-label={locked ? `Day ${d.day}, locked, opens ${d.dateShort}` : `Day ${d.day}, open`}
    >
      <span className="tile-scrim" />
      <span className="tile-stamp">{String(d.day).padStart(3, "0")}</span>

      {locked ? (
        <React.Fragment>
          <span className="tile-daysleft">{d.day - currentDay}</span>
          <span className="tile-foot">
            <span className="tile-lock">✕</span>
            <span className="tile-open">OPENS {d.dateShort}</span>
          </span>
        </React.Fragment>
      ) : (
        <span className="tile-foot">
          <span className="tile-date">{d.dateShort}</span>
          {status === "played"
            ? <span className="tile-tag played">♪ PLAYED</span>
            : <span className="tile-tag fresh">▶ PLAY</span>}
        </span>
      )}
    </button>
  );
}

/* ---------- month tab menu ---------- */
function MonthTabs({ groups, activeKey, onSelect, currentDay }) {
  return (
    <nav className="monthtabs">
      {groups.map(g => {
        const real = g.days.filter(d => !d.filler);
        const unlockedIn = real.filter(d => d.day <= currentDay).length;
        const isCurrent = real.some(d => d.day === currentDay);
        return (
          <button key={g.key} type="button"
                  className={`monthtab ${g.key === activeKey ? "is-active" : ""} ${isCurrent ? "is-current" : ""}`}
                  onClick={() => onSelect(g.key)}>
            <span className="monthtab-label">{g.label}</span>
            <span className="monthtab-count">{unlockedIn}/{real.length}</span>
          </button>
        );
      })}
    </nav>
  );
}

/* ---------- month header above its grid ---------- */
function MonthHead({ group, unlockedIn }) {
  const realCount = group.days.filter(d => !d.filler).length;
  return (
    <div className="season-head">
      <h2 className="display season-name">{group.label}</h2>
      <div className="season-rule"><span /></div>
      <div className="season-count">{unlockedIn}/{realCount}</div>
    </div>
  );
}

/* ---------- the grid, laid over one big shared picture per month ---------- */
function Windowpane({ group, monthKey, statusFor, onOpen, currentDay }) {
  const [cols, setCols] = useState(() => colsForWidth(window.innerWidth));
  const [editingPhoto, setEditingPhoto] = useState(false);
  const photoRef = useRef(null);
  useEffect(() => {
    const onResize = () => setCols(colsForWidth(window.innerWidth));
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  useEffect(() => {
    if (photoRef.current) {
      photoRef.current.setAttribute("class", "windowpane-photo");
      photoRef.current.style.cssText = "position:absolute;inset:0;width:100%;height:100%;display:block;";
    }
  }, [monthKey]);
  const packed = group.kickoff
    ? group.days.map((d, i) => ({ ...d, gridColumn: `${i + 1} / span 1`, gridRow: "1 / span 1" }))
    : window.packRows(group.days, cols);

  return (
    <div className={`windowpane ${group.kickoff ? "is-kickoff" : ""} ${editingPhoto ? "is-editing-photo" : ""}`}>
      <image-slot ref={photoRef} id={`month-photo-${monthKey}`} shape="rect" class="windowpane-photo"
        placeholder="drop this month's picture here"></image-slot>
      <div className="grid" style={{ "--cols": group.kickoff ? group.days.length : cols }}>
        {packed.map((d, i) => (
          <Tile key={d.day ?? `filler-${i}`} d={d} status={statusFor(d)} onOpen={onOpen} currentDay={currentDay} />
        ))}
      </div>
      <button type="button" className="photo-edit-btn"
              onClick={() => setEditingPhoto(v => !v)}
              style={{ display: IS_AUTHOR ? "block" : "none" }}>
        {editingPhoto ? "✓ done" : "🖼 change photo"}
      </button>
    </div>
  );
}
function colsForWidth(w) { return w < 480 ? 4 : w < 700 ? 4 : w < 1000 ? 5 : 6; }

/* Loads the YouTube IFrame API once, shared by every player instance. */
let ytApiPromise = null;
function loadYtApi() {
  if (window.YT && window.YT.Player) return Promise.resolve(window.YT);
  if (ytApiPromise) return ytApiPromise;
  ytApiPromise = new Promise((resolve) => {
    const prev = window.onYouTubeIframeAPIReady;
    window.onYouTubeIframeAPIReady = () => { if (prev) prev(); resolve(window.YT); };
    const s = document.createElement("script");
    s.src = "https://www.youtube.com/iframe_api";
    document.head.appendChild(s);
  });
  return ytApiPromise;
}

/* Embeds via the IFrame API so we can catch "embedding disabled" (error 100/101/150)
   and fall back to a tap-through card instead of showing YouTube's broken-player UI. */
function YtPlayer({ id, title }) {
  const mountRef = useRef(null);
  const playerRef = useRef(null);
  const [blocked, setBlocked] = useState(false);

  useEffect(() => {
    let cancelled = false;
    setBlocked(false);
    loadYtApi().then((YT) => {
      if (cancelled || !mountRef.current) return;
      playerRef.current = new YT.Player(mountRef.current, {
        videoId: id,
        playerVars: { rel: 0, modestbranding: 1 },
        events: {
          onError: (e) => { if ([100, 101, 150].includes(e.data)) setBlocked(true); },
        },
      });
    });
    return () => { cancelled = true; if (playerRef.current && playerRef.current.destroy) playerRef.current.destroy(); };
  }, [id]);

  if (blocked) {
    return (
      <div className="modal-noembed">
        <Cassette size={70} />
        <span>this one can't play here — tap below to open it on YouTube</span>
      </div>
    );
  }
  return <div ref={mountRef} title={title} />;
}

/* ---------- song reveal modal ---------- */
function SongModal({ d, status, onClose, showNotes }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [onClose]);

  if (!d) return null;
  const song = d.song;
  const search = song
    ? (song.yt
        ? `https://music.youtube.com/watch?v=${song.yt}`
        : `https://www.youtube.com/results?search_query=${encodeURIComponent(song.title + " " + song.artist)}`)
    : null;

  return (
    <div className="modal-veil" onClick={onClose}>
      <div className="modal-card" onClick={(e) => e.stopPropagation()}>
        <button className="modal-x" onClick={onClose} aria-label="Close">✕</button>

        <div className="modal-spine">
          <span>DAY {String(d.day).padStart(3, "0")}</span>
          <span className="modal-spine-date">{d.dateLong}</span>
        </div>

        {song ? (
          <div className="modal-grid">
            <div className="modal-player">
              {song.yt ? (
                <YtPlayer key={song.yt} id={song.yt} title={song.title} />
              ) : (
                <div className="modal-noembed">
                  <Cassette size={70} />
                  <span>tap below to play on YouTube</span>
                </div>
              )}
            </div>

            <div className="modal-meta">
              <div className="modal-track">
                <div className="modal-label">TRACK {String(d.day).padStart(3, "0")}</div>
                <h3 className="display modal-songtitle">{song.title}</h3>
                <div className="modal-artist">{song.artist}</div>
              </div>

              {showNotes && song.note && <p className="hand modal-note">{song.note}</p>}

              <a className="btn-play" href={search} target="_blank" rel="noreferrer">
                ▶ Play on YouTube
              </a>
            </div>
          </div>
        ) : (
          <div className="modal-empty">
            <Cassette size={74} />
            <h3 className="display">No song set for this day — yet</h3>
            <p>Add one in <code>calendar-data.jsx</code> → <code>EXAMPLE_SONGS[{d.day}]</code>,
               with a title, artist, a YouTube id and your note.</p>
          </div>
        )}
      </div>
    </div>
  );
}

/* ---------- preview / time-travel bar ---------- */
function PreviewBar({ realDay, useReal, setUseReal, simDay, setSimDay }) {
  if (!IS_AUTHOR) return null;
  const simDate = window.dateForDay(Math.min(Math.max(simDay, 1), window.TOTAL_DAYS));
  return (
    <div className="previewbar">
      <div className="pv-tag">PREVIEW</div>
      <button className={`pv-toggle ${useReal ? "on" : ""}`} onClick={() => setUseReal(!useReal)}>
        {useReal ? "● using real date" : "○ using real date"}
      </button>
      {!useReal && (
        <div className="pv-scrub">
          <span className="pv-readout">pretend it's <b>day {simDay}</b> · {window.fmtLong(simDate)}</span>
          <input type="range" min="0" max={window.TOTAL_DAYS} value={simDay}
                 onChange={(e) => setSimDay(Number(e.target.value))} />
        </div>
      )}
      {useReal && (
        <span className="pv-readout">
          {realDay <= 0 ? "the year hasn't started yet — everything's wrapped" : `day ${realDay} of ${window.TOTAL_DAYS} unlocked`}
        </span>
      )}
    </div>
  );
}

Object.assign(window, { Cassette, Editable, IntroNote, Tile, MonthTabs, MonthHead, Windowpane, SongModal, PreviewBar });
