// lazy.jsx — load view/panel scripts on demand instead of all up front
//
// Every .jsx file is compiled in the browser by Babel, so each one costs download + compile
// time before first paint. Only the always-visible pieces (data, people, sync, App) load at
// startup; views, edit panels and display mode load when first needed. The normal app then
// prefetches the rest in idle time so switching tabs stays instant; display screens don't.

const LAZY = {
  views: {
    dashboard: ['done-pile.jsx', 'dashboard.jsx'],
    tasks:     ['done-pile.jsx', 'tasks.jsx'],
    board:     ['done-pile.jsx', 'board.jsx'],
    calendar:  ['calendar.jsx'],
  },
  panels: ['task-panel.jsx', 'project-panel.jsx'],
  display: ['display.jsx'],
};

const lazyLoaded = new Set();
const lazyPending = {};

// Fetch, compile and run one script (once). Its top-level functions/globals become available
// exactly as if it had been a <script type="text/babel"> tag.
function loadJsx(src) {
  if (lazyLoaded.has(src)) return Promise.resolve();
  if (!lazyPending[src]) {
    lazyPending[src] = fetch(src, { cache: 'no-cache' })
      .then(r => { if (!r.ok) throw new Error(`${src}: HTTP ${r.status}`); return r.text(); })
      .then(code => {
        const out = Babel.transform(code, { presets: ['react'], filename: src }).code;
        const el = document.createElement('script');
        el.text = out + '\n//# sourceURL=' + src;
        document.head.appendChild(el);
        lazyLoaded.add(src);
      })
      .catch(e => { delete lazyPending[src]; throw e; });
  }
  return lazyPending[src];
}

// Returns { ready, error, retry } for a group of scripts; starts loading when `enabled`.
function useLazyScripts(srcs, enabled = true) {
  const key = srcs.join(',');
  const [, bump] = React.useReducer(x => x + 1, 0);
  const [error, setError] = React.useState(null);
  const [attempt, setAttempt] = React.useState(0);
  const ready = srcs.every(s => lazyLoaded.has(s));
  React.useEffect(() => {
    if (!enabled || ready) return;
    let alive = true;
    setError(null);
    Promise.all(srcs.map(loadJsx)).then(() => alive && bump(), e => alive && setError(e));
    return () => { alive = false; };
  }, [key, enabled, ready, attempt]);
  return { ready, error, retry: () => setAttempt(a => a + 1) };
}

// Load scripts one per idle slot so compiling never blocks typing or scrolling.
function prefetchJsx(srcs) {
  const queue = [...new Set(srcs)].filter(s => !lazyLoaded.has(s));
  const idle = window.requestIdleCallback || ((fn) => setTimeout(fn, 200));
  const next = () => {
    const src = queue.shift();
    if (!src) return;
    idle(() => loadJsx(src).catch(() => {}).then(next));
  };
  next();
}

function LazyFallback({ state, label }) {
  if (state && state.error) {
    return (
      <div className="nb-lazy-fallback">
        couldn't load {label || 'this page'} — {String(state.error.message || state.error)}
        <button className="nb-act" style={{ marginLeft: 12 }} onClick={state.retry}>retry</button>
      </div>
    );
  }
  return <div className="nb-lazy-fallback">turning the page…</div>;
}

// Focus an element that may not exist yet (e.g. inside a view that's still loading).
function focusWhenReady(id, timeoutMs = 3000) {
  const start = Date.now();
  const tick = () => {
    const el = document.getElementById(id);
    if (el) el.focus();
    else if (Date.now() - start < timeoutMs) setTimeout(tick, 50);
  };
  setTimeout(tick, 30);
}

Object.assign(window, { LAZY, loadJsx, useLazyScripts, prefetchJsx, LazyFallback, focusWhenReady });
