// ForestLynk pre-auth — main app.
// Owns: interactive prototype wrappers, the design canvas layout,
// and the Tweaks panel.

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "tagline": "link",
  "signInVariant": "canonical",
  "monospaceHandle": true,
  "suggestEnabled": true,
  "immutability": "observational",
  "whatIsHandle": "short",
  "liveUniqueness": true,
  "confirmOnSubmit": false,
  "rememberedEmail": false
}/*EDITMODE-END*/;

// ─────────────────────────── Interactive sign-in ───────────────────────────
function InteractiveSignIn({ t }) {
  const REMEMBERED = 'acorn@example.com';
  const [email, setEmail] = useState(t.rememberedEmail ? REMEMBERED : '');
  const [state, setState] = useState(t.rememberedEmail ? 'remembered' : 'empty');
  const [resendIn, setResendIn] = useState(24);
  const [focused, setFocused] = useState(false);
  const timersRef = useRef([]);

  // Stay in sync if the tweak flips
  useEffect(() => {
    if (t.rememberedEmail && state === 'empty' && !email) {
      setEmail(REMEMBERED);
      setState('remembered');
    } else if (!t.rememberedEmail && state === 'remembered') {
      setEmail('');
      setState('empty');
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [t.rememberedEmail]);

  const tagline = TAGLINES[t.tagline] ?? TAGLINES.link;

  // Countdown effect while awaiting
  useEffect(() => {
    if (state !== 'awaiting' && state !== 'awaiting-resent') return;
    if (resendIn <= 0) { setState('awaiting-ready'); return; }
    const id = setTimeout(() => setResendIn(s => s - 1), 1000);
    timersRef.current.push(id);
    return () => clearTimeout(id);
  }, [resendIn, state]);

  const onEmailChange = (v) => {
    setEmail(v);
    setFocused(true);
    if (!v) { setState('empty'); return; }
    setState(EMAIL_RE.test(v) ? 'typing-valid' : 'typing-invalid');
  };

  const send = () => {
    setState('submitting');
    const id = setTimeout(() => {
      setState('awaiting');
      setResendIn(24);
    }, 1100);
    timersRef.current.push(id);
  };

  const resend = () => {
    setState('awaiting-resent');
    setResendIn(24);
    const id = setTimeout(() => setState('awaiting'), 1100);
    timersRef.current.push(id);
  };

  const editEmail = () => {
    setState(EMAIL_RE.test(email) ? 'typing-valid' : 'empty');
  };

  const useDifferent = () => {
    setEmail('');
    setState('empty');
    setFocused(false);
  };

  const reset = () => {
    timersRef.current.forEach(clearTimeout);
    timersRef.current = [];
    setEmail('');
    setState('empty');
    setResendIn(24);
  };

  // The deep-link return shows the welcome-back beat for 1.6s, then resets.
  const [postAuth, setPostAuth] = useState(null);  // null | 'beat-back' | 'beat-claim'
  const simulateReturn = (kind) => {
    setPostAuth(kind);
    const id = setTimeout(() => { setPostAuth(null); reset(); }, 1800);
    timersRef.current.push(id);
  };

  return (
    <Phone label="Magic-link · prototype">
      {postAuth === 'beat-back'  && <WelcomeBackBeat handle="acorn-37" monospace={t.monospaceHandle}/>}
      {postAuth === 'beat-claim' && <HandleSuccessBeat handle="new arrival" monospace={t.monospaceHandle}/>}
      {!postAuth && (
        <SignInScreen
          state={state}
          email={email}
          tagline={tagline}
          variant={t.signInVariant}
          resendIn={resendIn}
          focused={focused}
          onEmailChange={onEmailChange}
          onSubmit={send}
          onResend={resend}
          onEditEmail={editEmail}
          onUseDifferent={useDifferent}
        />
      )}
      {/* demo affordance — only visible while awaiting */}
      {!postAuth && (state === 'awaiting' || state === 'awaiting-ready' || state === 'awaiting-resent') && (
        <DemoLinkRibbon
          onReturning={() => simulateReturn('beat-back')}
          onFirstTime={() => simulateReturn('beat-claim')}
        />
      )}
    </Phone>
  );
}

function DemoLinkRibbon({ onReturning, onFirstTime }) {
  return (
    <div style={{
      position: 'absolute', left: 18, right: 18, bottom: 32, zIndex: 30,
      background: 'rgba(58,42,7,0.85)', border: '0.5px solid #6B5018',
      borderRadius: 10, padding: '8px 10px',
      display: 'flex', alignItems: 'center', gap: 8,
      font: '500 10px var(--font-sans)',
      backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
    }}>
      <span style={{
        color: '#F4DCA0', letterSpacing: '0.08em', textTransform: 'uppercase',
      }}>demo</span>
      <span style={{ color: '#D6BFA8', flex: 1 }}>Simulate tapping the email link:</span>
      <button onClick={onReturning} style={demoBtnStyle}>returning</button>
      <button onClick={onFirstTime} style={demoBtnStyle}>first-time</button>
    </div>
  );
}

const demoBtnStyle = {
  background: 'rgba(212,150,12,0.18)', border: '0.5px solid #6B5018',
  color: '#F4DCA0', padding: '4px 8px', borderRadius: 6,
  font: '500 10px var(--font-sans)', cursor: 'pointer', whiteSpace: 'nowrap',
};

// ─────────────────────────── Interactive claim ───────────────────────────
function InteractiveClaim({ t }) {
  const [handle, setHandle] = useState('');
  const [checkState, setCheckState] = useState('idle');   // idle | checking | available | taken | server-error
  const [status, setStatus] = useState('idle');           // idle | submitting | success
  const [showConfirm, setShowConfirm] = useState(false);
  const [focused, setFocused] = useState(false);
  const timersRef = useRef([]);
  const debounceRef = useRef(null);

  const validity = validateHandle(handle);

  // Debounced live-uniqueness check.
  useEffect(() => {
    if (debounceRef.current) clearTimeout(debounceRef.current);
    if (!t.liveUniqueness) { setCheckState('idle'); return; }
    if (validity !== 'ok') { setCheckState('idle'); return; }
    setCheckState('checking');
    debounceRef.current = setTimeout(() => {
      setCheckState(TAKEN_SAMPLE.has(handle.toLowerCase()) ? 'taken' : 'available');
    }, 650);
    return () => clearTimeout(debounceRef.current);
  }, [handle, t.liveUniqueness, validity]);

  const onChange = (v) => {
    const cleaned = v.toLowerCase().slice(0, HANDLE_MAX);
    setHandle(cleaned);
    setFocused(true);
  };

  const onSuggest = () => {
    const suggestions = ['oak-23', 'mist-44', 'sapling_18', 'cedar-91', 'lichen-7'];
    const pick = suggestions[Math.floor(Math.random() * suggestions.length)];
    setHandle(pick);
  };

  const onSubmit = () => {
    if (t.confirmOnSubmit) { setShowConfirm(true); return; }
    doClaim();
  };

  const doClaim = () => {
    setShowConfirm(false);
    setStatus('submitting');
    const id = setTimeout(() => {
      // 1/4 of the time simulate collision so the path is visible
      if (TAKEN_SAMPLE.has(handle.toLowerCase())) {
        setCheckState('taken');
        setStatus('idle');
        return;
      }
      setStatus('success');
      const r = setTimeout(reset, 2000);
      timersRef.current.push(r);
    }, 1100);
    timersRef.current.push(id);
  };

  const reset = () => {
    timersRef.current.forEach(clearTimeout);
    timersRef.current = [];
    setHandle('');
    setStatus('idle');
    setCheckState('idle');
  };

  return (
    <Phone label="Claim handle · prototype">
      {status === 'success'
        ? <HandleSuccessBeat handle={handle} monospace={t.monospaceHandle}/>
        : <ClaimHandleScreen
            handle={handle}
            onChange={onChange}
            onSubmit={onSubmit}
            onSuggest={onSuggest}
            onCancel={reset}
            checkState={checkState}
            status={status}
            focused={focused}
            monospace={t.monospaceHandle}
            suggestEnabled={t.suggestEnabled}
            immutabilityCopy={IMMUTABILITY_COPY[t.immutability]}
            whatIsHandleCopy={WHAT_IS_HANDLE_COPY[t.whatIsHandle]}
          />}
      {showConfirm && (
        <ConfirmClaimModal
          handle={handle}
          monospace={t.monospaceHandle}
          onConfirm={doClaim}
          onCancel={() => setShowConfirm(false)}
          loading={status === 'submitting'}
        />
      )}
    </Phone>
  );
}

// ─────────────────────────── Static state mockups ───────────────────────────

// Magic-link · states
function SignInStateRow() {
  return (
    <DCSection id="signin-states" title="Magic-link · every state"
               subtitle="Each phone is the same screen, frozen at a different moment.">
      <DCArtboard id="empty"        label="01 · empty"             width={410} height={820}>
        <Phone><SignInScreen state="empty" email=""/></Phone>
      </DCArtboard>
      <DCArtboard id="remembered"   label="02 · remembered email"  width={410} height={820}>
        <Phone><SignInScreen state="remembered" email="acorn@example.com"/></Phone>
      </DCArtboard>
      <DCArtboard id="typing-invalid" label="03 · typing (invalid)" width={410} height={820}>
        <Phone><SignInScreen state="typing-invalid" email="acorn@" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="typing-valid" label="04 · typing (valid)"     width={410} height={820}>
        <Phone><SignInScreen state="typing-valid" email="acorn@example.com" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="submitting"   label="05 · submitting"         width={410} height={820}>
        <Phone><SignInScreen state="submitting" email="acorn@example.com"/></Phone>
      </DCArtboard>
      <DCArtboard id="error"        label="06 · server error"       width={410} height={820}>
        <Phone><SignInScreen state="error" email="acorn@example.com"
                             errorMessage="Couldn't reach the inbox right now. Try again."/></Phone>
      </DCArtboard>
      <DCArtboard id="awaiting"     label="07 · awaiting (24s)"     width={410} height={820}>
        <Phone><SignInScreen state="awaiting" email="acorn@example.com" resendIn={24}/></Phone>
      </DCArtboard>
      <DCArtboard id="awaiting-ready" label="08 · awaiting · resend ready" width={410} height={820}>
        <Phone><SignInScreen state="awaiting-ready" email="acorn@example.com"/></Phone>
      </DCArtboard>
      <DCArtboard id="awaiting-resent" label="09 · just resent"     width={410} height={820}>
        <Phone><SignInScreen state="awaiting-resent" email="acorn@example.com" resendIn={24}/></Phone>
      </DCArtboard>
    </DCSection>
  );
}

// Stale magic-link landings — what users see when the link they tapped isn't fresh.
function StaleLinkRow() {
  return (
    <DCSection id="stale-link" title="Stale magic-link landings"
               subtitle="What the user sees when the link they tapped is past its 15-minute window, has already been used, or arrived mangled. Calm — failing to sign in isn't framed as an error.">
      <DCArtboard id="expired" label="A · Expired (>15 min)" width={410} height={820}>
        <Phone><ExpiredLinkScreen kind="expired" email="acorn@example.com"/></Phone>
      </DCArtboard>
      <DCArtboard id="used" label="B · Already used" width={410} height={820}>
        <Phone><ExpiredLinkScreen kind="used" email="acorn@example.com"/></Phone>
      </DCArtboard>
      <DCArtboard id="invalid" label="C · Malformed / unrecognized" width={410} height={820}>
        <Phone><ExpiredLinkScreen kind="invalid" email="acorn@example.com"/></Phone>
      </DCArtboard>
    </DCSection>
  );
}

// Claim-handle · states
function ClaimStateRow() {
  return (
    <DCSection id="claim-states" title="Claim-handle · every state"
               subtitle="Real-time uniqueness checking surfaces three live cues: checking, available, taken.">
      <DCArtboard id="c-empty"       label="01 · empty"            width={410} height={820}>
        <Phone><ClaimHandleScreen handle="" checkState="idle"/></Phone>
      </DCArtboard>
      <DCArtboard id="c-format"      label="02 · format error"     width={410} height={820}>
        <Phone><ClaimHandleScreen handle="-ac" checkState="idle" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="c-checking"    label="03 · checking (debounced)" width={410} height={820}>
        <Phone><ClaimHandleScreen handle="oak-23" checkState="checking" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="c-available"   label="04 · available"        width={410} height={820}>
        <Phone><ClaimHandleScreen handle="oak-23" checkState="available" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="c-taken"       label="05 · taken (collision)" width={410} height={820}>
        <Phone><ClaimHandleScreen handle="acorn-37" checkState="taken" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="c-reserved"    label="06 · reserved"         width={410} height={820}>
        <Phone><ClaimHandleScreen handle="admin" checkState="idle" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="c-submitting"  label="07 · submitting"       width={410} height={820}>
        <Phone><ClaimHandleScreen handle="oak-23" checkState="available" status="submitting" focused/></Phone>
      </DCArtboard>
      <DCArtboard id="c-confirm"     label="08 · confirm sheet"    width={410} height={820}>
        <Phone>
          <ClaimHandleScreen handle="oak-23" checkState="available" focused/>
          <ConfirmClaimModal handle="oak-23" monospace onCancel={()=>{}} onConfirm={()=>{}}/>
        </Phone>
      </DCArtboard>
      <DCArtboard id="c-success"     label="09 · success beat"     width={410} height={820}>
        <Phone><HandleSuccessBeat handle="oak-23"/></Phone>
      </DCArtboard>
    </DCSection>
  );
}

// Visual variants for sign-in
function SignInVariantRow() {
  return (
    <DCSection id="signin-variants" title="Sign-in · visual takes"
               subtitle="Same flow, three hero treatments. The canonical pairing (lockup + lowercase h1) is the established login pattern; the others propose room to breathe.">
      <DCArtboard id="v-canonical" label="A · Canonical (recommended)" width={410} height={820}>
        <Phone><SignInScreen state="empty" variant="canonical"/></Phone>
      </DCArtboard>
      <DCArtboard id="v-threshold" label="B · Threshold (breathing mark)" width={410} height={820}>
        <Phone><SignInScreen state="empty" variant="threshold"/></Phone>
      </DCArtboard>
      <DCArtboard id="v-voice" label="C · Voice (serif speaks)" width={410} height={820}>
        <Phone><SignInScreen state="empty" variant="voice"/></Phone>
      </DCArtboard>
      <DCArtboard id="v-grain" label="D · Topo (contour ground)" width={410} height={820}>
        <Phone><SignInScreen state="empty" variant="grain"/></Phone>
      </DCArtboard>
    </DCSection>
  );
}

// Welcome-back row
function CallbackRow() {
  return (
    <DCSection id="callback" title="Deep-link return"
               subtitle="A 1–2s beat between auth callback and main tabs — separate beats for returning vs. first-time users.">
      <DCArtboard id="welcome-back" label="Returning · welcome back" width={410} height={820}>
        <Phone><WelcomeBackBeat handle="acorn-37"/></Phone>
      </DCArtboard>
      <DCArtboard id="success-claim" label="First-time · post-claim" width={410} height={820}>
        <Phone><HandleSuccessBeat handle="oak-23"/></Phone>
      </DCArtboard>
    </DCSection>
  );
}

// ─────────────────────────── App ───────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  return (
    <>
      <DesignCanvas>
        <DCSection id="hero" title="ForestLynk pre-auth flow"
                   subtitle="Two cold-launch screens. The left phone is interactive — type, submit, watch the awaiting state, tap the demo ribbon to simulate the link tap. The right phone runs the first-time handle-claim with debounced uniqueness checking. Use Tweaks (bottom-right) to switch hero treatment, copy, monospace, and confirm-modal behaviour.">
          <DCArtboard id="hero-signin" label="① Magic-link · live" width={410} height={820}>
            <InteractiveSignIn t={t}/>
          </DCArtboard>
          <DCArtboard id="hero-claim" label="② Claim handle · live (first-time only)" width={410} height={820}>
            <InteractiveClaim t={t}/>
          </DCArtboard>
        </DCSection>

        <SignInStateRow/>
        <StaleLinkRow/>
        <ClaimStateRow/>
        <SignInVariantRow/>
        <CallbackRow/>

        <DCSection id="notes" title="Brief notes & open questions"
                   subtitle="A small wall of post-its on the open questions from the brief.">
          <DCPostIt width={300}>
            <b>No-cache returning user.</b><br/>
            Magic-link folds sign-in and sign-up into the same form, so this edge gets hidden. We surface it two ways: a <i>remembered-email</i> recognition card when the address is in local storage (one-tap relog, plus an escape hatch for shared devices), and three calm <i>stale-link</i> landings when an old link gets tapped. Failing to sign in is a condition, not an alarm.
          </DCPostIt>
          <DCPostIt width={300}>
            <b>Two screens or one?</b><br/>
            One — the form → awaiting swap stays on the same route. Lighter, no router noise. The mark is anchored so the transition feels like a single screen rearranging itself, not a new page.
          </DCPostIt>
          <DCPostIt width={300}>
            <b>Validation rhythm.</b><br/>
            Inline, but quiet. The CTA greys out; the helper line only appears once the user has typed 5+ chars and the value's still malformed — so we don't yell at someone mid-keystroke.
          </DCPostIt>
          <DCPostIt width={300}>
            <b>Deep-link return.</b><br/>
            Worth a 1.6s beat. The mark held still + "Welcome back, @handle" + "Your forest is where you left it." First-time users land on the post-claim beat instead.
          </DCPostIt>
          <DCPostIt width={300}>
            <b>Already signed in?</b><br/>
            Routing should prevent it. If they land here anyway, the screen still renders — but a one-liner under the wordmark could say "Already signed in as @handle — open forest →" as a calm escape hatch. Not in v1.
          </DCPostIt>
          <DCPostIt width={300}>
            <b>First-launch context.</b><br/>
            The mark is enough. Adding an "It's a place-rooted social app" line front-loads onboarding into a moment that should be quiet. Save that copy for a one-time bottom-sheet after first sign-in.
          </DCPostIt>
          <DCPostIt width={300}>
            <b>Real-time uniqueness.</b><br/>
            Worth the extra endpoint. The 650ms debounce hides the request entirely for fast typists, and the "checking → ✓ available" beat is calmer than a delayed surprise error on submit. Reserve the modal confirmation for if research shows users regret the claim.
          </DCPostIt>
        </DCSection>
      </DesignCanvas>

      <TweaksPanel>
        <TweakSection label="Sign-in"/>
        <TweakToggle label="Remembered email (no cache → soft sign-in)" value={t.rememberedEmail}
                     onChange={v => setTweak('rememberedEmail', v)}/>
        <TweakSelect label="Hero treatment" value={t.signInVariant}
          options={[
            { value: 'canonical', label: 'A · Canonical (recommended)' },
            { value: 'threshold', label: 'B · Threshold (breathing)' },
            { value: 'voice',     label: 'C · Voice (serif speaks)' },
            { value: 'grain',     label: 'D · Topo (contour ground)' },
          ]}
          onChange={v => setTweak('signInVariant', v)}/>
        <TweakSelect label="Tagline copy" value={t.tagline}
          options={[
            { value: 'link', label: 'A link in your inbox is the whole sign-in.' },
            { value: 'send', label: "We'll send a link to your inbox." },
            { value: 'sign', label: 'Sign in with a link in your inbox.' },
            { value: 'none', label: '— none —' },
          ]}
          onChange={v => setTweak('tagline', v)}/>

        <TweakSection label="Claim handle"/>
        <TweakToggle label="Monospace handle field" value={t.monospaceHandle}
                     onChange={v => setTweak('monospaceHandle', v)}/>
        <TweakToggle label="Suggest-a-handle button" value={t.suggestEnabled}
                     onChange={v => setTweak('suggestEnabled', v)}/>
        <TweakToggle label="Real-time uniqueness check" value={t.liveUniqueness}
                     onChange={v => setTweak('liveUniqueness', v)}/>
        <TweakToggle label="Confirm-modal on submit" value={t.confirmOnSubmit}
                     onChange={v => setTweak('confirmOnSubmit', v)}/>
        <TweakSelect label="Immutability copy" value={t.immutability}
          options={[
            { value: 'terse',         label: "This can't be changed later." },
            { value: 'warm',          label: 'Choose carefully — handles are permanent.' },
            { value: 'observational', label: "You'll keep this as long as you're on ForestLynk." },
          ]}
          onChange={v => setTweak('immutability', v)}/>
        <TweakSelect label='"What is a handle?"' value={t.whatIsHandle}
          options={[
            { value: 'short',  label: 'Others see this everywhere…' },
            { value: 'longer', label: "It's how every node, chip…" },
            { value: 'plain',  label: 'Your name across ForestLynk…' },
          ]}
          onChange={v => setTweak('whatIsHandle', v)}/>
      </TweaksPanel>
    </>
  );
}

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