// app.jsx — state machine, config, tweaks, theme application
const { useState: useStateA, useEffect: useEffectA, useRef: useRefA } = React;

const CFG_DEFAULT = {
  platform: 'Reclova',
  company: 'Reclova',
  role: 'Loading…',
  duration: 30,
  questions: 4,
  maxReschedule: 2,
  candidate: { name: 'Candidate', first: 'there', email: '' },
  recruiter: { name: 'Hiring Team', title: 'Talent Team', email: '' },
};

const ACCENTS = {
  Coral: { '--accent': 'oklch(0.635 0.145 38)', '--accent-hover': 'oklch(0.575 0.152 36)', '--accent-soft': 'oklch(0.94 0.045 48)', '--accent-ring': 'oklch(0.635 0.145 38 / 0.3)', '--accent-fg': 'oklch(0.99 0.012 60)' },
  Navy:  { '--accent': 'oklch(0.43 0.11 268)',  '--accent-hover': 'oklch(0.37 0.115 268)', '--accent-soft': 'oklch(0.93 0.04 268)',  '--accent-ring': 'oklch(0.43 0.11 268 / 0.3)',  '--accent-fg': 'oklch(0.99 0.01 268)'  },
  Green: { '--accent': 'oklch(0.52 0.11 150)',  '--accent-hover': 'oklch(0.46 0.115 150)', '--accent-soft': 'oklch(0.93 0.045 150)', '--accent-ring': 'oklch(0.52 0.11 150 / 0.3)',  '--accent-fg': 'oklch(0.99 0.01 150)'  },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "theme": "reclova", "accent": "Coral", "font": "geist" }/*EDITMODE-END*/;

function getInterviewToken() {
  const parts = window.location.pathname.split('/').filter(Boolean);
  return parts.length >= 2 ? parts[1] : null;
}

// See screens-interview.jsx for why this can no longer assume same-origin.
const API_BASE = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
  ? `${window.location.protocol}//${window.location.hostname}:8000`
  : (window.BACKEND_URL || 'https://recruiter-api.reclova.com');

function makeSessionKey() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random() * 16 | 0;
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}

const TAB_SESSION_KEY = makeSessionKey();

// ── Scheduled-waiting screen (shown when interview is scheduled but not yet joinable) ──
function ScheduledWaitingScreen({ cfg, onStartNow, onReschedule }) {
  const [now, setNow] = useStateA(new Date());
  useEffectA(() => {
    const t = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(t);
  }, []);

  const scheduledAt = cfg.scheduledStart ? new Date(cfg.scheduledStart) : null;
  const minutesUntil = scheduledAt ? Math.max(0, Math.round((scheduledAt - now) / 60000)) : null;

  const fmt = (d) => d.toLocaleString('en-IN', { weekday:'long', day:'numeric', month:'long', hour:'2-digit', minute:'2-digit' });

  return (
    <div style={{ width: '100%' }}>
      <TopBar right={<span style={{ fontSize: 13, color: 'var(--text-faint)', fontWeight: 500 }}>Need help? <a href="mailto:support@reclova.com" style={{ color: 'var(--accent)', textDecoration: 'none' }}>Contact support</a></span>} />
      <Screen max={560} style={{ padding: '48px 24px 80px' }}>
        {/* calendar icon */}
        <div style={{ width: 72, height: 72, borderRadius: 20, background: 'var(--accent-soft)', color: 'var(--accent)', display: 'grid', placeItems: 'center', marginBottom: 24, animation: 'popIn .4s cubic-bezier(.2,.8,.3,1.4)' }}>
          <Icon name="calendar" size={36} stroke={1.8} />
        </div>
        <h1 style={{ fontSize: 'clamp(26px,4vw,34px)', fontFamily: 'var(--font-display)', fontWeight: 500, letterSpacing: '-0.01em', color: 'var(--text)', margin: 0, textAlign: 'center' }}>
          Your interview is scheduled
        </h1>
        <p style={{ fontSize: 15.5, color: 'var(--text-dim)', marginTop: 12, marginBottom: 28, textAlign: 'center', lineHeight: 1.65, textWrap: 'pretty' }}>
          Hi {cfg.candidate.first}, you're all set for your <strong style={{ color: 'var(--text)' }}>{cfg.role}</strong> interview at <strong style={{ color: 'var(--text)' }}>{cfg.company}</strong>.
        </p>

        <Card style={{ width: '100%' }} pad={24}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 18 }}>
            <div style={{ width: 48, height: 48, borderRadius: 14, background: 'var(--accent)', display: 'grid', placeItems: 'center', flexShrink: 0 }}>
              <Icon name="clock" size={22} style={{ color: 'var(--accent-fg)' }} />
            </div>
            <div>
              <div style={{ fontSize: 13, color: 'var(--text-faint)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 3 }}>Scheduled for</div>
              <div style={{ fontSize: 17, fontWeight: 700, color: 'var(--text)', lineHeight: 1.3 }}>
                {scheduledAt ? fmt(scheduledAt) : 'Time TBD'}
              </div>
            </div>
          </div>

          {minutesUntil !== null && (
            <div style={{ background: minutesUntil <= 15 ? 'var(--accent-soft)' : 'var(--surface-2)', borderRadius: 12, padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 10 }}>
              {minutesUntil <= 15 ? (
                <>
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--accent)', animation: 'recPulse 1.4s ease-in-out infinite', flexShrink: 0 }} />
                  <span style={{ fontSize: 13.5, color: 'var(--accent)', fontWeight: 600 }}>
                    Your interview link is active — {minutesUntil <= 0 ? 'starting now!' : `starts in ${minutesUntil} minutes`}
                  </span>
                </>
              ) : (
                <>
                  <Icon name="clock" size={15} stroke={2} style={{ color: 'var(--text-faint)', flexShrink: 0 }} />
                  <span style={{ fontSize: 13, color: 'var(--text-faint)' }}>
                    Link activates 10 minutes before · {minutesUntil} minutes to go
                  </span>
                </>
              )}
            </div>
          )}

          <div style={{ height: 1, background: 'var(--border)', margin: '18px 0' }} />

          <div style={{ fontSize: 13, color: 'var(--text-faint)', lineHeight: 1.65 }}>
            A reminder email will be sent to <strong style={{ color: 'var(--text)' }}>{cfg.candidate.email}</strong> 10 minutes before your interview.
          </div>

          <div style={{ height: 1, background: 'var(--border)', margin: '18px 0' }} />

          {/* Reschedule row */}
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
            <div>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--text)' }}>Need a different time?</div>
              <div style={{ fontSize: 12.5, color: 'var(--text-faint)', marginTop: 2 }}>
                {cfg.maxReschedule > 0
                  ? `You can reschedule up to ${cfg.maxReschedule} time${cfg.maxReschedule > 1 ? 's' : ''}.`
                  : 'No reschedules remaining.'}
              </div>
            </div>
            <Button size="sm" variant="secondary" icon="refresh"
              onClick={onReschedule}
              disabled={cfg.maxReschedule <= 0}>
              Reschedule
            </Button>
          </div>
        </Card>

        <div style={{ marginTop: 20, width: '100%', display: 'flex', flexDirection: 'column', gap: 10 }}>
          {/* Within 10-min window → primary "Start now" button is the main CTA */}
          {(minutesUntil === null || minutesUntil <= 10) && (
            <Button full icon="play" onClick={onStartNow}>Start interview now</Button>
          )}

          <Button full variant="secondary" icon="calendar" onClick={() => window.location.reload()}>
            Refresh status
          </Button>
        </div>

        {/* Outside 10-min window → offer early start as a subtle text option */}
        {minutesUntil !== null && minutesUntil > 10 && (
          <div style={{ marginTop: 18, textAlign: 'center' }}>
            <button onClick={onStartNow} style={{
              background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'var(--font)',
              fontSize: 14, color: 'var(--text-dim)', display: 'inline-flex', alignItems: 'center', gap: 6,
            }}>
              Actually, start now instead
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
            </button>
          </div>
        )}

        <p style={{ fontSize: 12.5, color: 'var(--text-faint)', textAlign: 'center', marginTop: 14, lineHeight: 1.6 }}>
          The AI interviewer is always ready — you're not bound to the scheduled time.
        </p>
      </Screen>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [screen, setScreen] = useStateA('landing');
  const [slot, setSlot] = useStateA(null);
  const [attempts, setAttempts] = useStateA(0);
  const [stats, setStats] = useStateA(null);
  const [cfg, setCfg] = useStateA(CFG_DEFAULT);
  const [loading, setLoading] = useStateA(true);
  const [loadError, setLoadError] = useStateA(null);
  const [accessDenied, setAccessDenied] = useStateA(null); // 'completed' | 'in_progress_elsewhere'

  const heartbeatRef = useRefA(null);
  const interviewActiveRef = useRefA(false);

  // Single in-flight/resolved PROMISE for the livekit-token fetch, memoized in a ref so it
  // is only ever created once no matter how many places ask for it or how the timing lines
  // up. This used to be a plain state value passed as a prop: the check-screen prefetch and
  // the interview-screen's own fetch-on-mount could both see it as still null (state update
  // hadn't landed yet) and both fire their own POST — which dispatches TWO agents into the
  // same LiveKit room (duplicate transcript turns, interleaved AI speech).
  const lkTokenPromiseRef = useRefA(null);
  const ensureLkToken = (token) => {
    if (!lkTokenPromiseRef.current) {
      lkTokenPromiseRef.current = fetch(`${API_BASE}/api/v1/public/interview/${token}/livekit-token`, { method: 'POST' })
        .then(r => r.json())
        .catch((e) => ({ error: e.message }));
    }
    return lkTokenPromiseRef.current;
  };

  const startHeartbeat = (token) => {
    stopHeartbeat();
    heartbeatRef.current = setInterval(() => {
      if (!interviewActiveRef.current) return;
      fetch(`${API_BASE}/api/v1/public/interview/${token}/heartbeat`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ session_key: TAB_SESSION_KEY }), keepalive: true,
      }).catch(() => {});
    }, 20000);
  };

  const stopHeartbeat = () => {
    if (heartbeatRef.current) { clearInterval(heartbeatRef.current); heartbeatRef.current = null; }
  };

  // Release lock on tab close
  useEffectA(() => {
    const token = getInterviewToken();
    if (!token) return;
    const unlock = () => {
      if (!interviewActiveRef.current) return;
      navigator.sendBeacon(
        `${API_BASE}/api/v1/public/interview/${token}/unlock`,
        new Blob([JSON.stringify({ session_key: TAB_SESSION_KEY })], { type: 'application/json' })
      );
    };
    window.addEventListener('pagehide', unlock);
    window.addEventListener('beforeunload', unlock);
    return () => {
      window.removeEventListener('pagehide', unlock);
      window.removeEventListener('beforeunload', unlock);
      stopHeartbeat();
    };
  }, []);

  // Load interview data
  useEffectA(() => {
    const token = getInterviewToken();
    if (!token) {
      // No token in the URL — never show placeholder/demo candidate data on a
      // real candidate-facing link. A bare /interview or /interview/ path is
      // not a valid interview; show the same "invalid link" state a genuinely
      // unknown token gets, not a fabricated invitation someone could click
      // "Start the interview" on.
      setLoadError('No interview token in the URL.');
      setLoading(false);
      return;
    }

    fetch(`${API_BASE}/api/v1/public/interview/${token}`)
      .then(r => { if (!r.ok) throw new Error(`Interview not found (${r.status})`); return r.json(); })
      .then(async (data) => {
        // ── Already completed ──
        if (data.status === 'completed') {
          setAccessDenied('completed');
          setLoading(false);
          return;
        }

        const candidate = data.candidate || {};
        const job = data.job || {};
        // Title-case defensively — resumes with heavily-tracked ALL CAPS headers can
        // still yield an all-caps (or, pre-normalization, truncated) full_name.
        const toTitleCase = (s) => s.replace(/\S+/g, (w) => (
          w === w.toUpperCase() || w === w.toLowerCase()
            ? w[0].toUpperCase() + w.slice(1).toLowerCase()
            : w
        ));
        const firstName = toTitleCase(candidate.full_name || 'Candidate').split(' ')[0];
        const newCfg = {
          platform: 'Reclova',
          company: job.workspace_name || 'the company',
          role: job.title || 'the role',
          duration: 30,
          questions: data.question_count || 4,
          maxReschedule: 2,
          interviewToken: token,
          scheduledStart: data.scheduled_start || null,
          isJoinable: data.is_joinable !== false, // default true if not provided
          hasTranscript: data.has_transcript || false,
          candidate: { name: toTitleCase(candidate.full_name || 'Candidate'), first: firstName, email: candidate.email || '' },
          recruiter: { name: 'Hiring Team', title: `Talent Team · ${job.workspace_name || 'Reclova'}`, email: '' },
        };
        setCfg(newCfg);

        // ── Resume in-progress interview ──
        if (data.status === 'in_progress') {
          const lockResp = await fetch(`${API_BASE}/api/v1/public/interview/${token}/lock`, {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ session_key: TAB_SESSION_KEY }),
          }).then(r => r.json()).catch(() => ({ ok: true }));

          if (!lockResp.ok) {
            setAccessDenied(lockResp.reason === 'completed' ? 'completed' : 'in_progress_elsewhere');
            setLoading(false);
            return;
          }
          // Lock granted — resume directly to interview screen
          interviewActiveRef.current = true;
          startHeartbeat(token);
          setLoading(false);
          setScreen('interview'); // jump straight in
          return;
        }

        // ── Scheduled but not yet joinable ──
        if (data.status === 'scheduled' && data.is_joinable === false) {
          setLoading(false);
          setScreen('scheduled_waiting');
          return;
        }

        setLoading(false);
      })
      .catch(err => { setLoadError(err.message); setLoading(false); });
  }, []);

  const go = (s) => {
    window.scrollTo({ top: 0 });

    if (s === 'interview' && cfg.interviewToken) {
      interviewActiveRef.current = true;
      fetch(`${API_BASE}/api/v1/public/interview/${cfg.interviewToken}/lock`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ session_key: TAB_SESSION_KEY }),
      })
        .then(r => r.json())
        .then(d => {
          if (!d.ok) {
            interviewActiveRef.current = false;
            setAccessDenied(d.reason === 'completed' ? 'completed' : 'in_progress_elsewhere');
          } else {
            startHeartbeat(cfg.interviewToken);
            setScreen('interview');
          }
        })
        .catch(() => { startHeartbeat(cfg.interviewToken); setScreen('interview'); });
      return;
    }

    if (screen === 'interview') {
      interviewActiveRef.current = false;
      stopHeartbeat();
      if (cfg.interviewToken) {
        fetch(`${API_BASE}/api/v1/public/interview/${cfg.interviewToken}/unlock`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ session_key: TAB_SESSION_KEY }),
        }).catch(() => {});
      }
    }

    setScreen(s);

    if (s === 'check' && cfg.interviewToken) {
      ensureLkToken(cfg.interviewToken); // single-flight — safe to call even if already in-flight/resolved
    }
  };

  const onConfirmSchedule = (s) => { setSlot(s); setAttempts(a => a + 1); go('scheduled'); };

  const vars = { ...themeVars(t.theme, t.font), ...(ACCENTS[t.accent] || ACCENTS.Coral) };
  const isInterview = screen === 'interview';

  if (loading) return (
    <div style={{ ...vars, fontFamily: 'var(--font)', background: 'var(--bg)', color: 'var(--text)', minHeight: '100dvh', display: 'grid', placeItems: 'center' }}>
      <div style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
        <div style={{ width: 36, height: 36, borderRadius: '50%', border: '3px solid var(--border)', borderTopColor: 'var(--accent)', animation: 'spin 0.8s linear infinite' }} />
        <p style={{ fontSize: 15, color: 'var(--text-dim)', margin: 0 }}>Loading your interview…</p>
      </div>
    </div>
  );

  // ── Already completed ──
  if (accessDenied === 'completed') return (
    <div style={{ ...vars, fontFamily: 'var(--font)', background: 'var(--bg)', color: 'var(--text)', minHeight: '100dvh', display: 'grid', placeItems: 'center' }}>
      <div style={{ textAlign: 'center', maxWidth: 460, padding: '0 24px' }}>
        <div style={{ width: 64, height: 64, borderRadius: 20, background: 'oklch(0.93 0.08 145)', color: 'oklch(0.48 0.14 145)', display: 'grid', placeItems: 'center', margin: '0 auto 24px' }}>
          <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
        </div>
        <h2 style={{ fontSize: 24, fontFamily: 'var(--font-display)', fontWeight: 500, margin: '0 0 12px', color: 'var(--text)' }}>Interview already completed</h2>
        <p style={{ fontSize: 15, color: 'var(--text-dim)', margin: 0, lineHeight: 1.7 }}>You've already submitted this interview. Your responses are being reviewed by the hiring team and you'll hear back soon.</p>
        <p style={{ fontSize: 13, color: 'var(--text-faint)', marginTop: 20 }}>If you believe this is an error, please contact the recruiting team directly.</p>
      </div>
    </div>
  );

  // ── In-progress on another device ──
  if (accessDenied === 'in_progress_elsewhere') return (
    <div style={{ ...vars, fontFamily: 'var(--font)', background: 'var(--bg)', color: 'var(--text)', minHeight: '100dvh', display: 'grid', placeItems: 'center' }}>
      <div style={{ textAlign: 'center', maxWidth: 460, padding: '0 24px' }}>
        <div style={{ width: 64, height: 64, borderRadius: 20, background: 'oklch(0.95 0.06 80)', color: 'oklch(0.58 0.16 75)', display: 'grid', placeItems: 'center', margin: '0 auto 24px' }}>
          <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
        </div>
        <h2 style={{ fontSize: 24, fontFamily: 'var(--font-display)', fontWeight: 500, margin: '0 0 12px', color: 'var(--text)' }}>Interview already in progress</h2>
        <p style={{ fontSize: 15, color: 'var(--text-dim)', margin: 0, lineHeight: 1.7 }}>This interview is currently active on another browser or device. Only one session is allowed at a time.</p>
        <p style={{ fontSize: 13, color: 'var(--text-faint)', marginTop: 20 }}>Return to the original browser window. If you closed it accidentally, wait 60 seconds and refresh this page.</p>
      </div>
    </div>
  );

  if (loadError) return (
    <div style={{ ...vars, fontFamily: 'var(--font)', background: 'var(--bg)', color: 'var(--text)', minHeight: '100dvh', display: 'grid', placeItems: 'center' }}>
      <div style={{ textAlign: 'center', maxWidth: 420, padding: '0 24px' }}>
        <div style={{ width: 56, height: 56, borderRadius: 16, background: 'var(--danger-soft)', color: 'var(--danger)', display: 'grid', placeItems: 'center', margin: '0 auto 20px' }}><Icon name="alert" size={26} stroke={2} /></div>
        <h2 style={{ fontSize: 22, fontFamily: 'var(--font-display)', fontWeight: 500, margin: '0 0 10px', color: 'var(--text)' }}>Interview link invalid</h2>
        <p style={{ fontSize: 15, color: 'var(--text-dim)', margin: 0, lineHeight: 1.6 }}>
          This link may have expired or the interview was not found. Please check the email you received or{' '}
          <a href="mailto:support@reclova.com" style={{ color: 'var(--accent)', textDecoration: 'none', fontWeight: 600 }}>contact support</a>.
        </p>
      </div>
    </div>
  );

  const screens = {
    landing:           <LandingScreen go={go} cfg={cfg} />,
    schedule:          <ScheduleScreen go={go} cfg={cfg} attempts={attempts} onConfirm={onConfirmSchedule} />,
    scheduled:         slot ? <ScheduledScreen go={go} cfg={cfg} slot={slot} attempts={attempts} /> : <LandingScreen go={go} cfg={cfg} />,
    scheduled_waiting: <ScheduledWaitingScreen cfg={{ ...cfg, maxReschedule: Math.max(0, cfg.maxReschedule - attempts) }} onStartNow={() => go('check')} onReschedule={() => { setAttempts(a => a + 1); go('schedule'); }} />,
    recruiter:         <RecruiterScreen go={go} cfg={cfg} />,
    check:             <SystemCheckScreen go={go} cfg={cfg} />,
    interview:         <LiveInterviewScreen go={go} cfg={cfg} onComplete={setStats} getLkToken={() => ensureLkToken(cfg.interviewToken)} />,
    completed:         <CompletedScreen go={go} cfg={cfg} stats={stats} />,
  };

  return (
    <div style={{ ...vars, fontFamily: 'var(--font)', background: 'var(--bg)', color: 'var(--text)', minHeight: '100dvh', width: '100%', position: 'relative' }}>
      {!isInterview && <div style={{ minHeight: '100dvh', display: 'flex', flexDirection: 'column' }}>{screens[screen] || screens.landing}</div>}
      {isInterview && screens.interview}

      <TweaksPanel>
        <TweakSection label="Theme" />
        <TweakRadio label="Mode" value={t.theme} options={['reclova', 'dark']} onChange={v => setTweak('theme', v)} />
        <TweakRadio label="Accent" value={t.accent} options={['Coral', 'Navy', 'Green']} onChange={v => setTweak('accent', v)} />
        <TweakSection label="Typography" />
        <div style={{ fontSize: 10.5, color: 'rgba(41,38,27,.5)', lineHeight: 1.4 }}>Headings use the Newsreader serif. Pick the body sans:</div>
        <TweakRadio label="Body font" value={t.font} options={['geist', 'hanken', 'jakarta']} onChange={v => setTweak('font', v)} />
        <TweakSection label="Jump to screen" />
        <TweakSelect label="Screen" value={screen}
          options={['landing','schedule','scheduled','scheduled_waiting','recruiter','check','interview','completed']}
          onChange={v => { if (v === 'scheduled' && !slot) setSlot({ dateLabel: 'Mon, Jun 8', time: '11:00', tz: 'Asia/Kolkata (GMT+5:30)' }); go(v); }} />
      </TweaksPanel>
    </div>
  );
}

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