// Music landing page — original design.
// Bold display type, monochrome service marks, responsive split/stack,
// auto-palette from cover art, release-state-aware tagline.

const { useState, useEffect, useRef, useMemo } = React;

// ─────────────────────────────────────────────────────────────────────
// Tweak defaults — editable on disk via host edit-mode protocol
// ─────────────────────────────────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "title": "MIDNIGHT BLOOM",
  "titleDisplay": "Midnight Bloom",
  "artist": "VESPER NORTH",
  "artistDisplay": "Vesper North",
  "releaseType": "SINGLE",
  "releaseDate": "2026-05-13",
  "catalog": "VN—007",
  "accentMode": "auto",
  "accent": "#c6ff3d",
  "accent2": "#7ad7ff",
  "layout": "auto",
  "background": "cover-blobs",
  "contextEnabled": true,
  "contextSource": "auto",
  "ctaPrimary": "spotify",
  "showFooter": true,
  "footerOwner": "© VESPER NORTH MUSIC",
  "modSize": 0.62,
  "modDim": 0.55
} /*EDITMODE-END*/;

// Manual-mode accent presets — same chroma family, different hues
const ACCENT_OPTIONS = ["#c6ff3d", "#7ad7ff", "#ff7ad1", "#ffb14e", "#a78bfa"];

// ─────────────────────────────────────────────────────────────────────
// Service catalog — split into streaming and store kinds
// ─────────────────────────────────────────────────────────────────────
const SERVICES = [
// Streaming
{ id: "spotify", name: "Spotify", icon: "spotify", action: "Listen", kind: "stream", url: "#" },
{ id: "apple", name: "Apple Music", icon: "apple", action: "Listen", kind: "stream", url: "#" },
{ id: "ytmusic", name: "YouTube Music", icon: "ytmusic", action: "Listen", kind: "stream", url: "#" },
{ id: "soundcloud", name: "SoundCloud", icon: "soundcloud", action: "Listen", kind: "stream", url: "#" },
{ id: "tidal", name: "Tidal", icon: "tidal", action: "Listen", kind: "stream", url: "#" },
{ id: "amazon", name: "Amazon Music", icon: "amazon", action: "Play", kind: "stream", url: "#" },
{ id: "deezer", name: "Deezer", icon: "deezer", action: "Play", kind: "stream", url: "#" },
{ id: "pandora", name: "Pandora", icon: "pandora", action: "Listen", kind: "stream", url: "#" },
{ id: "youtube", name: "YouTube", icon: "youtube", action: "Watch", kind: "stream", url: "#" },
// Store / purchase
{ id: "bandcamp", name: "Bandcamp", icon: "bandcamp", action: "Buy", kind: "store", url: "#" },
{ id: "itunes", name: "iTunes", icon: "itunes", action: "Buy", kind: "store", url: "#" }];


// ─────────────────────────────────────────────────────────────────────
// Context-swap copy — different greeting based on traffic source
// ─────────────────────────────────────────────────────────────────────
const CONTEXT_COPY = {
  tiktok: { tag: "FROM TIKTOK", msg: "You found the track. Pick your platform." },
  instagram: { tag: "FROM INSTAGRAM", msg: "Heard a clip? Stream the full thing below." },
  youtube: { tag: "FROM YOUTUBE", msg: "Saved you a click. Pick your platform." },
  twitter: { tag: "FROM X", msg: "All the links, one tap away." },
  reddit: { tag: "FROM REDDIT", msg: "Cheers for the click. Stream below." },
  email: { tag: "FROM THE LIST", msg: "Welcome, subscriber. Tap to listen." },
  qr: { tag: "QR LINK", msg: "Scanned in the wild — pick your platform." }
  // direct visitors get no banner — only show when we actually know where they came from
};

function getContextKey(override) {
  if (override && override !== "auto") return override === "direct" ? null : override;
  try {
    const u = new URL(location.href);
    const from = (u.searchParams.get("from") || u.searchParams.get("utm_source") || "").toLowerCase();
    if (from && CONTEXT_COPY[from]) return from;
  } catch (e) {}
  return null;
}

// ─────────────────────────────────────────────────────────────────────
// Release state — "OUT NOW" once we're at or past the release date,
// otherwise show a formatted release date.
// ─────────────────────────────────────────────────────────────────────
function parseReleaseDate(iso) {
  if (!iso) return null;
  // Treat YYYY-MM-DD as local date so the "release day" flips at local midnight,
  // not UTC midnight.
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
  if (!m) {
    const d = new Date(iso);
    return isNaN(d) ? null : d;
  }
  return new Date(+m[1], +m[2] - 1, +m[3]);
}

function formatReleaseDateShort(d) {
  if (!d) return "";
  return d.toLocaleDateString("en-US", { month: "long", day: "numeric" });
}

function formatReleaseDate(d) {
  if (!d) return "";
  const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
  return months[d.getMonth()] + " " + d.getDate();
}

function useReleaseState(iso) {
  return useMemo(() => {
    const d = parseReleaseDate(iso);
    if (!d) return { released: false, label: "" };
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    return {
      released: today >= d,
      label: formatReleaseDate(d)
    };
  }, [iso]);
}

// ─────────────────────────────────────────────────────────────────────
// Layout detection — flip on aspect ratio + a min-width floor
// ─────────────────────────────────────────────────────────────────────
function useLayoutMode(override) {
  const [mode, setMode] = useState("stack");
  useEffect(() => {
    function compute() {
      if (override === "split" || override === "stack") {
        setMode(override);
        return;
      }
      const w = window.innerWidth;
      const h = window.innerHeight;
      const wide = w / h > 1.05 && w > 760;
      setMode(wide ? "split" : "stack");
    }
    compute();
    window.addEventListener("resize", compute);
    return () => window.removeEventListener("resize", compute);
  }, [override]);
  return mode;
}

// ─────────────────────────────────────────────────────────────────────
// Watch the user-dropped cover image. Returns { src } whenever it changes.
// Polls the image-slot shadow root (no event API exposed).
// ─────────────────────────────────────────────────────────────────────
function useCoverImage(slotRef) {
  const [src, setSrc] = useState(null);
  useEffect(() => {
    let stopped = false;
    let last = null;
    function poll() {
      if (stopped) return;
      const el = slotRef.current;
      const img = el && el.shadowRoot && el.shadowRoot.querySelector("img");
      const next = img && img.src ? img.src : null;
      if (next !== last) {
        last = next;
        setSrc(next);
      }
      setTimeout(poll, 600);
    }
    poll();
    return () => {stopped = true;};
  }, []);
  return src;
}

// ─────────────────────────────────────────────────────────────────────
// Mirror cover into the page background blur
// ─────────────────────────────────────────────────────────────────────
function useCoverMirror(coverSrc, enabled) {
  useEffect(() => {
    const bg = document.getElementById("bg-art");
    if (!bg) return;
    if (!enabled || !coverSrc) {bg.classList.remove("on");return;}
    bg.style.backgroundImage = `url("${coverSrc}")`;
    bg.classList.add("on");
  }, [coverSrc, enabled]);
}

// ─────────────────────────────────────────────────────────────────────
// Extract palette from cover whenever it changes (only used in 'auto' mode)
// ─────────────────────────────────────────────────────────────────────
function useExtractedPalette(coverSrc) {
  const [palette, setPalette] = useState(null);
  useEffect(() => {
    let cancelled = false;
    if (!coverSrc || !window.extractPalette) {setPalette(null);return;}
    window.extractPalette(coverSrc).then((p) => {
      if (!cancelled) setPalette(p);
    });
    return () => {cancelled = true;};
  }, [coverSrc]);
  return palette;
}

// ─────────────────────────────────────────────────────────────────────
// Components
// ─────────────────────────────────────────────────────────────────────
function ContextBanner({ ctx }) {
  return (
    <div className="ctx">
      <span className="ctx-dot" />
      <span className="ctx-tag">{ctx.tag}</span>
      <span className="ctx-sep">/</span>
      <span className="ctx-msg">{ctx.msg}</span>
    </div>);

}

function ScrollHint() {
  const [visible, setVisible] = useState(true);
  useEffect(() => {
    const onScroll = () => {
      if (window.scrollY > 20) setVisible(false);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    const t = setTimeout(() => setVisible(false), 8000);
    return () => {
      window.removeEventListener("scroll", onScroll);
      clearTimeout(t);
    };
  }, []);
  return (
    <div className={"scroll-hint" + (visible ? "" : " gone")} aria-hidden="true">
      <span className="sh-text">MORE BELOW</span>
      <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <path d="M6 9l6 6 6-6" />
      </svg>
    </div>);

}

function CoverArt({ slotRef, size }) {
  return (
    <div className="cover-wrap" style={{ "--cover-size": size }}>
      <image-slot
        ref={slotRef}
        id="release-cover"
        shape="rounded"
        radius="6"
        placeholder="DROP COVER ART"
        style={{ width: "100%", height: "100%", display: "block" }}>
      </image-slot>
      <div className="cover-frame" aria-hidden="true" />
    </div>);

}

// ─────────────────────────────────────────────────────────────────────
// Title / artist contextual parsing
//   splitTitle("Midnight Bloom (Konka Remix)")
//     -> { main: "Midnight Bloom", mod: "(Konka Remix)" }
//   splitArtist("Vesper North feat. Konka")
//     -> { main: "Vesper North", mod: "feat. Konka" }
//   Supports ft / ft. / feat / feat. / featuring / w/ / with (case-insensitive)
// ─────────────────────────────────────────────────────────────────────
function splitTitle(s) {
  if (!s) return { main: "", mod: null };
  const m = /^([\s\S]*?)\s*(\([^()]+\)|\[[^\[\]]+\])\s*$/.exec(s);
  if (!m) return { main: s, mod: null };
  return { main: m[1].trim(), mod: m[2].trim() };
}
function splitArtist(s) {
  if (!s) return { main: "", mod: null };
  const m = /^([\s\S]+?)\s+(ft\.?|feat\.?|featuring|w\/|with)\s+([\s\S]+)$/i.exec(s);
  if (!m) return { main: s, mod: null };
  return { main: m[1].trim(), mod: (m[2] + " " + m[3]).trim() };
}

function TitleBlock({ tweaks, releaseState }) {
  // Tagline composition: "<TYPE> · OUT NOW"  OR  "<TYPE> · <DATE>"
  const right = releaseState.released ? "OUT NOW" : releaseState.label;
  const titleParts = splitTitle(tweaks.title);
  const artistParts = splitArtist(tweaks.artist);
  return (
    <div className="title-block">
      <div className="meta-row">
        <span className="cat">{tweaks.catalog}</span>
      </div>
      <h1 className="title">
        <span className="title-main" style={{ padding: "0px 10px 0px 0px" }}>{titleParts.main}</span>
        {titleParts.mod && <span className="title-mod" style={{ padding: "0px", opacity: "0.8" }}> {titleParts.mod}</span>}
      </h1>
      <div className="artist-row">
        <span className="by">BY</span>
        <span className="artist">
          <span className="artist-main" style={{ padding: "0px 10px 0px 0px" }}>{artistParts.main}</span>
          {artistParts.mod && <span className="artist-mod" style={{ opacity: "0.8" }}> {artistParts.mod}</span>}
        </span>
      </div>
      <div className="tagline">
        <span className="tag-bar" />
        <span className="tag-left">{tweaks.releaseType}</span>
        {right && <span className="tag-sep">·</span>}
        {right &&
        <span className={"tag-right" + (releaseState.released ? " is-out" : "")}>
            {right}
          </span>
        }
      </div>
    </div>);

}

function ServiceRow({ svc, primary, onClick }) {
  return (
    <a
      href={svc.url}
      className={"svc" + (primary ? " primary" : "")}
      onClick={onClick}>
      
      <span className="svc-num">{String(svc.index).padStart(2, "0")}</span>
      <span className="svc-icon">{window.Icon[svc.icon]}</span>
      <span className="svc-name">{svc.name}</span>
      <span className="svc-spacer" />
      <span className="svc-action">{svc.action}</span>
      <span className="svc-arrow" aria-hidden="true">
        <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M5 12h14" />
          <path d="M13 6l6 6-6 6" />
        </svg>
      </span>
    </a>);

}

function SectionHeader({ label, count, kind }) {
  return (
    <div className={"svc-section-head svc-section-" + kind}>
      <span className="sh-bar" />
      <span className="sh-label">{label}</span>
      <span className="sh-count">{String(count).padStart(2, "0")}</span>
    </div>);

}

function ServiceList({ primaryId }) {
  // Group services by kind. Primary CTA pops out above the streaming group.
  const { primary, streamItems, storeItems } = useMemo(() => {
    let idx = 0;
    const number = (s) => ({ ...s, index: ++idx });
    const primary = SERVICES.find((s) => s.id === primaryId);
    const rest = SERVICES.filter((s) => s.id !== primaryId);
    return {
      primary: primary ? number(primary) : null,
      streamItems: rest.filter((s) => s.kind === "stream").map(number),
      storeItems: rest.filter((s) => s.kind === "store").map(number)
    };
  }, [primaryId]);

  return (
    <div className="svc-list">
      {primary &&
      <>
          <SectionHeader label="ONE-TAP" count={1} kind="primary" />
          <ServiceRow svc={primary} primary onClick={(e) => e.preventDefault()} />
        </>
      }

      {streamItems.length > 0 &&
      <>
          <SectionHeader label="STREAM" count={streamItems.length} kind="stream" />
          {streamItems.map((s) =>
        <ServiceRow key={s.id} svc={s} onClick={(e) => e.preventDefault()} />
        )}
        </>
      }

      {storeItems.length > 0 &&
      <>
          <SectionHeader label="BUY" count={storeItems.length} kind="store" />
          {storeItems.map((s) =>
        <ServiceRow key={s.id} svc={s} onClick={(e) => e.preventDefault()} />
        )}
        </>
      }
    </div>);

}

function Footer({ owner }) {
  return (
    <div className="foot">
      <div className="foot-left">
        <span className="foot-mark" />
        <span>{owner}</span>
      </div>
      <div className="foot-right">
        <a href="#">FOLLOW</a>
        <span>·</span>
        <a href="#">MAILING LIST</a>
        <span>·</span>
        <a href="#">PRESS</a>
      </div>
    </div>);

}

// Small palette readout — shown in Tweaks panel when in auto mode
function PaletteReadout({ palette, mode }) {
  if (mode !== "auto") return null;
  if (!palette) {
    return (
      <div className="pal-readout">
        <span className="pal-empty">Drop a cover to extract palette</span>
      </div>);

  }
  return (
    <div className="pal-readout">
      <div className="pal-row">
        <span className="pal-swatch" style={{ background: palette.primary }} />
        <span className="pal-label">PRIMARY</span>
        <span className="pal-hex">{palette.primary.toUpperCase()}</span>
      </div>
      <div className="pal-row">
        <span className="pal-swatch" style={{ background: palette.secondary }} />
        <span className="pal-label">SECONDARY</span>
        <span className="pal-hex">
          {palette.secondary.toUpperCase()}
          <span className="pal-tag">{palette.source === "extracted" ? "EXTRACTED" : "COMPLEMENT"}</span>
        </span>
      </div>
    </div>);

}

// ─────────────────────────────────────────────────────────────────────
// Tweaks panel
// ─────────────────────────────────────────────────────────────────────
function Tweaks({ tweaks, setTweak, palette }) {
  const {
    TweaksPanel, TweakSection, TweakText, TweakRadio, TweakSelect, TweakColor, TweakToggle, TweakSlider
  } = window;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Release">
        <TweakText label="Title" value={tweaks.title} onChange={(v) => setTweak("title", v)} />
        <TweakText label="Title (display)" value={tweaks.titleDisplay} onChange={(v) => setTweak("titleDisplay", v)} />
        <TweakText label="Artist" value={tweaks.artist} onChange={(v) => setTweak("artist", v)} />
        <TweakText label="Artist (display)" value={tweaks.artistDisplay} onChange={(v) => setTweak("artistDisplay", v)} />
        <TweakSelect
          label="Type"
          value={tweaks.releaseType}
          options={["SINGLE", "EP", "ALBUM", "REMIX", "LIVE", "DEMO"]}
          onChange={(v) => setTweak("releaseType", v)} />
        
        <TweakText
          label="Release date"
          placeholder="YYYY-MM-DD"
          value={tweaks.releaseDate}
          onChange={(v) => setTweak("releaseDate", v)} />
        
        <TweakText label="Catalog #" value={tweaks.catalog} onChange={(v) => setTweak("catalog", v)} />
        <TweakText label="Footer credit" value={tweaks.footerOwner} onChange={(v) => setTweak("footerOwner", v)} />
      </TweakSection>

      <TweakSection label="Palette">
        <TweakRadio
          label="Accent mode"
          value={tweaks.accentMode}
          options={[
          { value: "auto", label: "Auto" },
          { value: "manual", label: "Manual" }]
          }
          onChange={(v) => setTweak("accentMode", v)} />
        
        <PaletteReadout palette={palette} mode={tweaks.accentMode} />
        {tweaks.accentMode === "manual" &&
        <>
            <TweakColor
            label="Primary"
            value={tweaks.accent}
            options={ACCENT_OPTIONS}
            onChange={(v) => setTweak("accent", v)} />
          
            <TweakColor
            label="Secondary"
            value={tweaks.accent2}
            options={ACCENT_OPTIONS}
            onChange={(v) => setTweak("accent2", v)} />
          
          </>
        }
      </TweakSection>

      <TweakSection label="Type detail">
        <TweakSlider
          label="Modifier size"
          value={Math.round(tweaks.modSize * 100)}
          min={35} max={95} step={1} unit="%"
          onChange={(v) => setTweak("modSize", v / 100)} />
        
        <TweakSlider
          label="Modifier dim"
          value={Math.round(tweaks.modDim * 100)}
          min={20} max={100} step={1} unit="%"
          onChange={(v) => setTweak("modDim", v / 100)} />
        
      </TweakSection>

      <TweakSection label="Layout">
        <TweakRadio
          label="Orientation"
          value={tweaks.layout}
          options={[
          { value: "auto", label: "Auto" },
          { value: "split", label: "Split" },
          { value: "stack", label: "Stack" }]
          }
          onChange={(v) => setTweak("layout", v)} />
        
        <TweakSelect
          label="Background"
          value={tweaks.background}
          options={[
          { value: "solid", label: "Solid dark" },
          { value: "blobs", label: "Drifting blobs" },
          { value: "cover", label: "Mirror cover (blurred)" },
          { value: "cover-blobs", label: "Cover + blobs" }]
          }
          onChange={(v) => setTweak("background", v)} />
        
        <TweakToggle label="Footer" value={tweaks.showFooter} onChange={(v) => setTweak("showFooter", v)} />
      </TweakSection>

      <TweakSection label="Smart link">
        <TweakSelect
          label="Primary CTA"
          value={tweaks.ctaPrimary}
          options={SERVICES.map((s) => ({ value: s.id, label: s.name }))}
          onChange={(v) => setTweak("ctaPrimary", v)} />
        
        <TweakToggle
          label="Context banner"
          value={tweaks.contextEnabled}
          onChange={(v) => setTweak("contextEnabled", v)} />
        
        <TweakSelect
          label="Visitor source"
          value={tweaks.contextSource}
          options={[
          { value: "auto", label: "Auto (?from= param)" },
          { value: "direct", label: "Direct (no banner)" },
          { value: "tiktok", label: "TikTok" },
          { value: "instagram", label: "Instagram" },
          { value: "youtube", label: "YouTube" },
          { value: "twitter", label: "X / Twitter" },
          { value: "reddit", label: "Reddit" },
          { value: "email", label: "Email list" },
          { value: "qr", label: "QR code" }]
          }
          onChange={(v) => setTweak("contextSource", v)} />
        
      </TweakSection>
    </TweaksPanel>);

}

// ─────────────────────────────────────────────────────────────────────
// Root
// ─────────────────────────────────────────────────────────────────────
function App() {
  const [tweaks, setTweaks] = window.useTweaks(TWEAK_DEFAULTS);
  const setTweak = (k, v) => setTweaks({ [k]: v });

  const layoutMode = useLayoutMode(tweaks.layout);
  const ctxKey = getContextKey(tweaks.contextSource);
  const ctx = ctxKey ? CONTEXT_COPY[ctxKey] : null;
  const releaseState = useReleaseState(tweaks.releaseDate);

  // Cover image + dependent effects
  const slotRef = useRef(null);
  const coverSrc = useCoverImage(slotRef);
  const palette = useExtractedPalette(coverSrc);
  const mirrorCover = tweaks.background === "cover" || tweaks.background === "cover-blobs";
  useCoverMirror(coverSrc, mirrorCover);

  // Browser tab title
  useEffect(() => {
    const a = tweaks.artistDisplay || tweaks.artist;
    const t = tweaks.titleDisplay  || tweaks.title;
    const d = parseReleaseDate(tweaks.releaseDate);
    const shortDate = formatReleaseDateShort(d);
    document.title = releaseState.released
      ? `${a} - ${t} | Out Now`
      : `Pre-Save | ${a} - ${t} | ${shortDate}`;
  }, [tweaks.artistDisplay, tweaks.artist, tweaks.titleDisplay, tweaks.title, tweaks.releaseDate, releaseState.released]);

  // Background blob layer
  useEffect(() => {
    const blobs = document.getElementById("bg-blobs");
    const wantBlobs = tweaks.background === "blobs" || tweaks.background === "cover-blobs";
    if (blobs) blobs.classList.toggle("on", wantBlobs);
  }, [tweaks.background]);

  // Apply effective accents to CSS vars. Auto mode uses extracted palette
  // when available; falls back to manual values until then.
  useEffect(() => {
    const useAuto = tweaks.accentMode === "auto" && palette;
    const a1 = useAuto ? palette.primary : tweaks.accent;
    const a2 = useAuto ? palette.secondary : tweaks.accent2;
    document.documentElement.style.setProperty("--accent", a1);
    document.documentElement.style.setProperty("--accent-2", a2);
    document.documentElement.style.setProperty("--accent-ink", "#0b0c10");
    // Also push to the blob layer
    const blobA = document.querySelector(".blob.a");
    const blobB = document.querySelector(".blob.b");
    if (blobA) blobA.style.background = a1;
    if (blobB) blobB.style.background = a2;
  }, [tweaks.accentMode, tweaks.accent, tweaks.accent2, palette]);

  // Modifier (parenthetical / featuring) styling vars
  useEffect(() => {
    document.documentElement.style.setProperty("--mod-size", String(tweaks.modSize));
    document.documentElement.style.setProperty("--mod-dim", String(tweaks.modDim));
  }, [tweaks.modSize, tweaks.modDim]);

  return (
    <div className={"layout layout-" + layoutMode}>
      <div className="pane pane-art">
        <CoverArt
          slotRef={slotRef}
          size={layoutMode === "split" ? "min(72svh, 52vw)" : "min(60vw, 60svh)"} />
        
        <div className="art-stamp">
          <span className="stamp-line">{tweaks.catalog}</span>
          <span className="stamp-line">
            {releaseState.released ? "OUT NOW" : releaseState.label}
          </span>
        </div>
        {layoutMode === "stack" && <ScrollHint />}
      </div>

      <div className="pane pane-info">
        <div className="info-inner">
          {tweaks.contextEnabled && ctx && <ContextBanner ctx={ctx} />}
          <TitleBlock tweaks={tweaks} releaseState={releaseState} />
          <ServiceList primaryId={tweaks.ctaPrimary} />
          {tweaks.showFooter && <Footer owner={tweaks.footerOwner} />}
        </div>
      </div>

      <Tweaks tweaks={tweaks} setTweak={setTweak} palette={palette} />
    </div>);

}

// Mount
const stage = document.getElementById("stage");
stage.innerHTML = "";
ReactDOM.createRoot(stage).render(<App />);