/* ============================================================
   CADENCE — Root app: routing, state, trades, tweaks
   ============================================================ */
(function () {
  const { useState, useEffect, useMemo, useCallback, useRef } = React;
  const requestJson = window.CAD_API.requestJson;
  const F = window.CAD.fmt;

  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "tradeUX": "simple",
    "chartStyle": "area",
    "accent": "#3B82F6",
    "density": "comfortable",
    "monoNums": true
  }/*EDITMODE-END*/;

  // accent presets -> oklch tuned to match palette
  const ACCENTS = {
    "#3B82F6": { blue: 'oklch(0.660 0.155 256)', bright: 'oklch(0.730 0.165 252)', dim: 'oklch(0.520 0.110 256)' }, // electric blue
    "#22C55E": { blue: 'oklch(0.755 0.155 158)', bright: 'oklch(0.820 0.165 158)', dim: 'oklch(0.560 0.110 158)' }, // emerald
    "#A78BFA": { blue: 'oklch(0.700 0.150 295)', bright: 'oklch(0.770 0.160 295)', dim: 'oklch(0.560 0.110 295)' }, // violet
    "#E0B341": { blue: 'oklch(0.800 0.120 88)', bright: 'oklch(0.860 0.125 88)', dim: 'oklch(0.640 0.090 88)' },   // gold
  };

  const INITIAL_TWEAKS = {
    ...TWEAK_DEFAULTS,
    ...(window.CAD.currentUser?.preferences || {}),
  };

  function App() {
    const cad = window.useCad();
    const [t, setTweak] = window.useTweaks(INITIAL_TWEAKS);
    const [view, setView] = useState('discover');
    const [songId, setSongId] = useState('satellite');
    const [query, setQuery] = useState('');
    const [playingId, setPlayingId] = useState(null);
    const [playing, setPlaying] = useState(false);
    const [progress, setProgress] = useState(28);
    const [audioTime, setAudioTime] = useState(0);
    const [audioDur, setAudioDur] = useState(0);
    const audioRef = useRef(null);

    const songs = cad.songs;
    const portfolio = cad.portfolio;
    const balance = portfolio.balance;

    const watch = useMemo(
      () => (portfolio.watchlist || []).map((item) => item.id),
      [portfolio.watchlist]
    );

    // holdings keyed by songId
    const holdings = useMemo(() => {
      const m = {};
      (portfolio.holdings || []).forEach((h) => { m[h.songId] = { shares: h.shares, avgCost: h.avgCost }; });
      return m;
    }, [portfolio.holdings]);

    const song = useMemo(() => songs.find((s) => s.id === songId) || songs[0], [songId, songs]);
    const ownedFor = (id) => (holdings[id] ? holdings[id].shares : 0);
    const playingSong = playingId ? songs.find((s) => s.id === playingId) : null;
    const hasRealAudio = !!(playingSong && playingSong.audioUrl);

    // ---- apply tweaks to CSS ----
    useEffect(() => {
      const root = document.documentElement;
      const a = ACCENTS[t.accent] || ACCENTS["#3B82F6"];
      root.style.setProperty('--blue', a.blue);
      root.style.setProperty('--blue-bright', a.bright);
      root.style.setProperty('--blue-dim', a.dim);
      root.style.setProperty('--density', t.density === 'compact' ? '0.78' : t.density === 'roomy' ? '1.15' : '1');
      root.style.setProperty('--font-num', t.monoNums ? "'JetBrains Mono', ui-monospace, monospace" : "'Hanken Grotesk', sans-serif");
    }, [t.accent, t.density, t.monoNums]);

    // ---- persist preferences ----
    useEffect(() => {
      window.CAD.currentUser = {
        ...(window.CAD.currentUser || {}),
        preferences: t,
      };
      requestJson('/api/preferences', {
        method: 'PATCH',
        body: JSON.stringify({ preferences: t }),
      }).catch(() => {
        window.toast?.({ kind: 'sell', title: 'Settings sync failed', desc: 'Preferences changed locally but were not saved to the server.' });
      });
    }, [t]);

    // ---- now playing progress (simulated only when there is no real audio) ----
    useEffect(() => {
      if (!playing || hasRealAudio) return;
      const iv = setInterval(() => setProgress((p) => (p >= 100 ? 0 : p + 0.4)), 400);
      return () => clearInterval(iv);
    }, [playing, hasRealAudio]);

    // ---- drive the real <audio> element for uploaded tracks ----
    useEffect(() => {
      const el = audioRef.current;
      if (!el) return;
      const url = playingSong && playingSong.audioUrl;
      if (url) {
        if (el.getAttribute('src') !== url) {
          el.src = url;
          setProgress(0);
          setAudioTime(0);
          setAudioDur(0);
        }
        if (playing) {
          el.play().catch(() => {});
        } else {
          el.pause();
        }
      } else {
        el.pause();
        if (el.getAttribute('src')) {
          el.removeAttribute('src');
          el.load();
        }
      }
    }, [playingId, playing, playingSong]);

    const go = useCallback((v) => { setView(v); document.querySelector('.content')?.scrollTo({ top: 0 }); }, []);
    const openSong = useCallback((id) => { setSongId(id); setView('song'); document.querySelector('.content')?.scrollTo({ top: 0 }); }, []);
    const playSong = useCallback((id) => {
      setPlayingId((cur) => { if (cur === id) { setPlaying((p) => !p); return id; } setPlaying(true); return id; });
    }, []);

    const toggleWatch = useCallback(async (id) => {
      const exists = watch.includes(id);
      try {
        await cad.watch(id, !exists);
      } catch (error) {
        window.toast({ kind: 'sell', title: 'Watchlist update failed', desc: 'The song could not be updated in your watchlist.' });
      }
    }, [watch, cad]);

    const onTrade = useCallback(async ({ side, shares }) => {
      if (shares < 1) return;
      try {
        const data = await cad.trade({ songId, side, shares });
        window.toast({
          kind: side,
          title: `${side === 'buy' ? 'Bought' : 'Sold'} ${shares.toLocaleString()} shares · ${song.title}`,
          desc: `${side === 'buy' ? 'Spent' : 'Received'} ${F.money(data.trade.total, 2)} at ${F.money(data.trade.price, 2)}/share`,
        });
      } catch (error) {
        const message = error?.message === 'insufficient_balance'
          ? 'You do not have enough balance to place this order.'
          : error?.message === 'insufficient_shares'
          ? 'You do not own enough shares to sell that quantity.'
          : 'The trade could not be completed.';
        window.toast({ kind: 'sell', title: 'Trade failed', desc: message });
      }
    }, [songId, song, cad]);

    // Map a checkout error code to a friendly message.
    const checkoutMessage = (error) => {
      switch (error?.message) {
        case 'stripe_not_configured': return 'Payments are not configured on this server yet.';
        case 'already_owned': return 'You already own this — check your library.';
        case 'cannot_buy_own_song':
        case 'cannot_buy_own_album': return 'You cannot buy your own music.';
        case 'song_not_for_sale':
        case 'album_not_for_sale': return 'This music is not available for purchase.';
        case 'song_not_individually_sold': return 'This track is only sold as part of its release.';
        default: return 'Checkout could not be started.';
      }
    };

    const onBuy = useCallback(async () => {
      try {
        window.toast({ kind: 'buy', title: 'Redirecting to secure checkout…', desc: `Completing your purchase of ${song.title} with Stripe.` });
        await cad.buySong(songId); // navigates to Stripe on success
      } catch (error) {
        window.toast({ kind: 'sell', title: 'Checkout failed', desc: checkoutMessage(error) });
      }
    }, [songId, song, cad]);

    const onBuyAlbum = useCallback(async (releaseId) => {
      try {
        window.toast({ kind: 'buy', title: 'Redirecting to secure checkout…', desc: 'Completing your purchase with Stripe.' });
        await cad.buyRelease(releaseId); // navigates to Stripe on success
      } catch (error) {
        window.toast({ kind: 'sell', title: 'Checkout failed', desc: checkoutMessage(error) });
      }
    }, [cad]);

    // ---- return from Stripe Checkout (?purchase=success|cancel) ----
    useEffect(() => {
      const params = new URLSearchParams(window.location.search);
      const status = params.get('purchase');
      if (!status) return;
      const clean = () => window.history.replaceState({}, '', window.location.pathname);

      if (status === 'cancel') {
        window.toast({ kind: 'sell', title: 'Checkout canceled', desc: 'No payment was taken.' });
        clean();
        return;
      }
      if (status !== 'success') { clean(); return; }

      // The webhook fulfills asynchronously; re-bootstrap a few times until the
      // purchase shows up (owned/library), then confirm.
      window.toast({ kind: 'buy', title: 'Payment received', desc: 'Unlocking your music…' });
      let cancelled = false;
      let tries = 0;
      const poll = async () => {
        tries += 1;
        try { await cad.refresh(); } catch (error) { /* keep trying */ }
        if (cancelled) return;
        if (tries >= 4) {
          window.toast({ kind: 'buy', title: 'Purchase complete', desc: 'Your music is now in your library.' });
          clean();
          return;
        }
        setTimeout(poll, 1500);
      };
      poll();
      return () => { cancelled = true; };
    }, []); // run once on mount

    // market index delta (avg change)
    const marketDelta = songs.reduce((a, s) => a + s.change, 0) / songs.length;

    // portfolio holdings array for portfolio view
    const holdingsArr = useMemo(() => Object.entries(holdings).filter(([, v]) => v.shares > 0).map(([id, v]) => {
      const s = songs.find((x) => x.id === id);
      return { songId: id, shares: v.shares, avgCost: v.avgCost, song: s, cost: v.shares * v.avgCost };
    }).filter((h) => h.song).sort((x, y) => (y.song.price * y.shares) - (x.song.price * x.shares)), [holdings, songs]);

    const onAudioTime = (e) => {
      const el = e.currentTarget;
      setAudioTime(el.currentTime || 0);
      if (el.duration && Number.isFinite(el.duration)) {
        setAudioDur(el.duration);
        setProgress((el.currentTime / el.duration) * 100);
      }
    };
    const onAudioEnded = () => { setPlaying(false); setProgress(0); setAudioTime(0); };

    // Real elapsed/total for the now-playing bar (only when real audio is loaded).
    const hasDur = audioDur > 0;
    const npElapsed = hasDur ? F.time(audioTime) : '0:00';
    const npTotal = hasDur ? F.time(audioDur) : (playingSong && playingSong.length) || '0:00';

    return (
      <div className="app">
        <window.UI.Sidebar view={view} go={go} />
        <div className="main">
          <window.UI.TopBar go={go} onSearch={(q) => { setQuery(q); if (q && view !== 'discover') setView('discover'); }} query={query} marketDelta={marketDelta} />
          <div className="content">
            {view === 'discover' && <window.Marketplace go={go} openSong={openSong} playSong={playSong} playingId={playing ? playingId : null} query={query} watch={watch} onWatch={toggleWatch} />}
            {view === 'song' && <window.SongDetail song={song} balance={balance} owned={ownedFor(songId)} onTrade={onTrade} onBuy={onBuy} onBuyAlbum={onBuyAlbum} playSong={playSong} playingId={playing ? playingId : null} progress={progress} audioTime={audioTime} audioDur={audioDur} tradeVariant={t.tradeUX} chartMode={t.chartStyle} go={go} watch={watch} onWatch={toggleWatch} />}
            {view === 'portfolio' && <window.Portfolio go={go} openSong={openSong} holdings={holdingsArr} balance={balance} watch={watch} />}
            {view === 'artist' && <window.ArtistStudio go={go} openSong={openSong} />}
            {view === 'upload' && <window.UploadMusic go={go} openSong={openSong} />}
            {view === 'profile' && <window.ProfilePage go={go} />}
            {playingSong && <div style={{ height: 80 }} />}
          </div>
        </div>

        <audio ref={audioRef} onTimeUpdate={onAudioTime} onLoadedMetadata={onAudioTime} onEnded={onAudioEnded} style={{ display: 'none' }} />
        <window.UI.NowPlaying song={playingSong} playing={playing} onToggle={() => setPlaying((p) => !p)} progress={progress} elapsed={npElapsed} total={npTotal} />
        <window.UI.MobileTabs view={view} go={go} />
        <window.UI.ToastHost />

        {/* Tweaks */}
        <window.TweaksPanel title="Tweaks">
          <window.TweakSection label="Trading UX" />
          <window.TweakRadio label="Order panel" value={t.tradeUX} options={['simple', 'pro', 'quick']} onChange={(v) => { setTweak('tradeUX', v); if (view !== 'song') openSong(songId); }} />
          <div style={{ fontSize: 11.5, color: 'var(--text-mute)', lineHeight: 1.5, marginTop: -4, marginBottom: 4 }}>
            {t.tradeUX === 'simple' && 'Clean buy/sell toggle with quantity & quick %.'}
            {t.tradeUX === 'pro' && 'Order ticket with live book, market/limit & size slider.'}
            {t.tradeUX === 'quick' && 'One-tap “invest $X” → auto-converts to shares.'}
          </div>
          <window.TweakSection label="Charts" />
          <window.TweakRadio label="Price chart" value={t.chartStyle} options={['area', 'line']} onChange={(v) => setTweak('chartStyle', v)} />
          <window.TweakSection label="Appearance" />
          <window.TweakColor label="Brand accent" value={t.accent} options={["#3B82F6", "#22C55E", "#A78BFA", "#E0B341"]} onChange={(v) => setTweak('accent', v)} />
          <window.TweakRadio label="Density" value={t.density} options={['compact', 'comfortable', 'roomy']} onChange={(v) => setTweak('density', v)} />
          <window.TweakToggle label="Mono numerals (terminal)" value={t.monoNums} onChange={(v) => setTweak('monoNums', v)} />
        </window.TweaksPanel>
      </div>
    );
  }

  function Root() {
    return (
      <window.CadProvider>
        <App />
      </window.CadProvider>
    );
  }

  const Mount = window.CAD._authed ? Root : window.AuthScreen;
  ReactDOM.createRoot(document.getElementById('root')).render(<Mount />);
})();
