// ── App shell + URL-backed router ────────────────────────────────
// Auth via Clerk (window.__clerk). Payments via PayPal Smart Buttons.

function parseLocation() {
  const pathname = window.location.pathname || '/';
  const search = new URLSearchParams(window.location.search);
  const parts = pathname.split('/').filter(Boolean);

  if (parts.length === 0) return { name: 'home', params: {} };
  if (parts[0] === 'signin') return { name: 'auth', params: { mode: 'signin' } };
  if (parts[0] === 'signup') return { name: 'auth', params: { mode: 'signup' } };
  if (parts[0] === 'studio') return { name: 'studio', params: {} };
  if (parts[0] === 'library') return { name: 'library', params: {} };
  if (parts[0] === 'paints') return { name: 'paints', params: {} };
  if (parts[0] === 'credits') return { name: 'credits', params: {} };
  if (parts[0] === 'editor') {
    return { name: 'editor', params: { seedBaseUrl: search.get('base') || null } };
  }
  if (parts[0] === 'design' && parts[1]) {
    return {
      name: 'result',
      params: {
        projectId: parts[1],
        difficulty: search.get('d') || null,
      },
    };
  }
  if ((parts[0] === 'share' || parts[0] === 'p') && parts[1]) {
    return { name: 'share', params: { projectId: parts[1] } };
  }
  return { name: 'home', params: {} };
}

function routeToPath(name, params = {}) {
  switch (name) {
    case 'landing':
    case 'home':       return '/';
    case 'auth':       return params.mode === 'signup' ? '/signup' : '/signin';
    case 'studio':     return '/studio';
    case 'library':    return '/library';
    case 'paints':     return '/paints';
    case 'credits':    return '/credits';
    case 'editor':     return params.seedBaseUrl
                          ? `/editor?base=${encodeURIComponent(params.seedBaseUrl)}`
                          : '/editor';
    case 'result':     return `/design/${params.projectId}${params.difficulty ? `?d=${params.difficulty}` : ''}`;
    case 'share':      return `/share/${params.projectId}`;
    default:           return '/';
  }
}

function App() {
  const [route, setRoute] = useState(() => parseLocation());
  const [user, setUser] = useState(null);
  const [credits, setCredits] = useState(null);
  const [projects, setProjects] = useState([]);
  const [projectsLoading, setProjectsLoading] = useState(false);
  const [booted, setBooted] = useState(false);
  const [bootError, setBootError] = useState(null);

  // Boot: load /api/config, fetch Clerk-js from your FAPI, init Clerk.
  useEffect(() => {
    (async () => {
      try {
        const cfg = await fetch('/api/config').then(r => r.json());
        if (!cfg.clerkPublishableKey) {
          throw new Error('Server missing CLERK_PUBLISHABLE_KEY');
        }
        await loadClerkScript(cfg.clerkPublishableKey);
        if (!window.Clerk) {
          throw new Error('Clerk JS failed to load — check network/CSP');
        }
        // When loaded with data-clerk-publishable-key, the bundle auto-creates
        // an instance and replaces window.Clerk with it (.load is a method).
        // When loaded raw, window.Clerk is the constructor — construct it.
        const clerk = typeof window.Clerk === 'function'
          ? new window.Clerk(cfg.clerkPublishableKey)
          : window.Clerk;
        await clerk.load();
        window.__clerk = clerk;

        if (clerk.user) {
          const { user: me, credits: balance } = await Api.me();
          setUser(me);
          setCredits(balance);
        }

        clerk.addListener(async (e) => {
          if (e.user) {
            try {
              const { user: me, credits: balance } = await Api.me();
              setUser(me);
              setCredits(balance);
            } catch {}
          } else {
            setUser(null);
            setCredits(null);
            setProjects([]);
          }
        });
      } catch (err) {
        setBootError(err.message);
      } finally {
        setBooted(true);
      }
    })();
  }, []);

  useEffect(() => {
    if (!user) return;
    setProjectsLoading(true);
    Api.listProjects()
      .then(list => setProjects(list.filter(hasRender).map(normalizeProject)))
      .catch(err => console.error('listProjects failed:', err))
      .finally(() => setProjectsLoading(false));
  }, [user]);

  useEffect(() => {
    const onPop = () => setRoute(parseLocation());
    window.addEventListener('popstate', onPop);
    return () => window.removeEventListener('popstate', onPop);
  }, []);

  function navigate(name, params = {}) {
    const path = routeToPath(name, params);
    if (path !== window.location.pathname + window.location.search) {
      window.history.pushState({}, '', path);
    }
    setRoute({ name: name === 'landing' ? 'home' : name, params });
    window.scrollTo({ top: 0, behavior: 'instant' });
  }

  function handleAuth(u) {
    setUser(u);
    refreshCredits();
    navigate('studio');
  }

  async function handleSignOut() {
    try { await window.__clerk?.signOut(); } catch {}
    setUser(null);
    setCredits(null);
    setProjects([]);
    navigate('home');
  }

  async function refreshCredits() {
    try {
      const { balance } = await Api.credits();
      setCredits(balance);
    } catch {}
  }

  function onProjectReady() {
    Api.listProjects()
      .then(list => setProjects(list.filter(hasRender).map(normalizeProject)))
      .catch(() => {});
    refreshCredits();
  }

  useEffect(() => {
    if (!booted) return;
    // Authed users may browse the marketing landing page; only bounce them off
    // the auth screen itself.
    if (user && route.name === 'auth') {
      navigate('studio');
      return;
    }
    if (!user && ['studio', 'library', 'paints', 'editor', 'result', 'credits'].includes(route.name)) {
      navigate('auth', { mode: 'signin' });
    }
  }, [user, route.name, booted]);

  if (!booted) {
    return <div style={{ padding: 80, color: 'var(--ink-3)', textAlign: 'center' }}>Loading studio…</div>;
  }
  if (bootError) {
    return (
      <div style={{ padding: 80, textAlign: 'center' }}>
        <div className="display" style={{ fontSize: 32, marginBottom: 12 }}><em>The workshop is closed</em></div>
        <div style={{ color: 'var(--ink-3)' }}>{bootError}</div>
      </div>
    );
  }

  // Auth uses the shared top nav. The landing page brings its own marketing nav
  // + footer (and renders for authed users too). App screens use the sidebar.
  const showTopNav = false; // landing + auth are self-contained (own nav)

  return (
    <div id="app">
      {showTopNav && (
        <Nav
          route={route}
          navigate={navigate}
          user={user}
          credits={credits}
          onSignOut={handleSignOut}
        />
      )}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }} key={`${route.name}:${route.params.projectId || route.params.seedBaseUrl || ''}`}>
        {route.name === 'home' && <Landing navigate={navigate} user={user} />}
        {route.name === 'auth' && <Auth navigate={navigate} initialMode={route.params.mode || 'signup'} onAuth={handleAuth} />}
        {route.name === 'studio' && user && (
          <Studio
            navigate={navigate}
            user={user}
            credits={credits}
            onSignOut={handleSignOut}
            projects={projects.length ? projects : []}
            loading={projectsLoading}
            onProjectChanged={onProjectReady}
          />
        )}
        {route.name === 'library' && user && (
          <Library
            navigate={navigate}
            user={user}
            credits={credits}
            onSignOut={handleSignOut}
            projects={projects}
            loading={projectsLoading}
            onProjectChanged={onProjectReady}
          />
        )}
        {route.name === 'paints' && user && (
          <Paints navigate={navigate} user={user} credits={credits} onSignOut={handleSignOut} />
        )}
        {route.name === 'credits' && user && (
          <Credits navigate={navigate} user={user} credits={credits} onSignOut={handleSignOut} onChange={refreshCredits} />
        )}
        {route.name === 'editor' && user && (
          <Editor
            navigate={navigate}
            user={user}
            credits={credits}
            onSignOut={handleSignOut}
            onProjectReady={onProjectReady}
            seedBaseUrl={route.params.seedBaseUrl}
          />
        )}
        {route.name === 'result' && user && (
          <Result
            navigate={navigate}
            user={user}
            credits={credits}
            onSignOut={handleSignOut}
            projectId={route.params.projectId}
            initialDifficulty={route.params.difficulty}
            onProjectChanged={onProjectReady}
          />
        )}
        {route.name === 'share' && (
          <PublicReport navigate={navigate} projectId={route.params.projectId} isAuthed={!!user} />
        )}
      </div>
    </div>
  );
}

// A design is only "saved" once a render was sent — i.e. it has at least one
// generated scheme (or painted plate). Upload-only rows are never surfaced.
function hasRender(row) {
  return Boolean(row.beginner_scheme || row.advanced_scheme || row.generated_beginner_image || row.generated_advanced_image);
}

function normalizeProject(row) {
  const scheme = row.advanced_scheme || row.beginner_scheme || {};
  const palette = (scheme.colors && scheme.colors.length)
    ? scheme.colors.slice(0, 5)
    : ['#C8324A', '#080806', '#7E7C7B', '#D9C8A8', '#C89E5A'];
  const painted = row.generated_advanced_image || row.generated_beginner_image;
  const image = painted || row.miniature_image;
  const difficulty = row.advanced_scheme ? 'Advanced' : (row.beginner_scheme ? 'Beginner' : 'Draft');
  const created = new Date(row.created_at);
  const updated = relativeTime(created);
  const steps =
    (Array.isArray(row.steps_advanced) ? row.steps_advanced.length : 0) +
    (Array.isArray(row.steps_beginner) ? row.steps_beginner.length : 0);

  return {
    id: row.id,
    name: scheme.theme || 'Untitled',
    archetype: scheme.description ? truncate(scheme.description, 60) : 'Custom miniature',
    palette,
    accent: palette[0],
    silhouette: 'knight',
    image,
    status: 'complete',
    difficulty,
    timeHours: difficulty === 'Advanced' ? 7 : 2,
    updated,
    started: created.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }),
    steps: steps || 6,
  };
}

// Loads Clerk's clerk.browser.js from the Frontend API URL encoded into the
// publishable key. The bundle exposes `window.Clerk` once loaded.
function loadClerkScript(publishableKey) {
  return new Promise((resolve, reject) => {
    if (window.Clerk) return resolve();
    // Publishable key format: pk_test_<base64(FAPI + '$')>
    const tail = publishableKey.replace(/^pk_(test|live)_/, '');
    let fapi;
    try {
      // atob may include a trailing '$'; strip it.
      fapi = atob(tail).replace(/\$$/, '');
    } catch (err) {
      return reject(new Error('Could not decode publishable key'));
    }
    if (!fapi) return reject(new Error('Empty FAPI in publishable key'));

    const script = document.createElement('script');
    script.async = true;
    script.crossOrigin = 'anonymous';
    script.setAttribute('data-clerk-publishable-key', publishableKey);
    script.src = `https://${fapi}/npm/@clerk/clerk-js@5/dist/clerk.browser.js`;
    script.onload = () => {
      // Wait one tick for the auto-init listener to populate window.Clerk.
      setTimeout(() => resolve(), 0);
    };
    script.onerror = () => reject(new Error(`Failed to load Clerk from ${fapi}`));
    document.head.appendChild(script);
  });
}

function relativeTime(date) {
  const diff = Date.now() - date.getTime();
  const sec = Math.floor(diff / 1000);
  if (sec < 60) return 'Just now';
  const min = Math.floor(sec / 60);
  if (min < 60) return `${min} min ago`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${hr}h ago`;
  const day = Math.floor(hr / 24);
  if (day === 1) return 'Yesterday';
  if (day < 7) return `${day} days ago`;
  if (day < 30) return `${Math.floor(day / 7)} week${day >= 14 ? 's' : ''} ago`;
  return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}

function truncate(s, max) {
  if (!s) return '';
  return s.length > max ? s.slice(0, max - 1) + '…' : s;
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
