/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import * as React from 'react';
import {
  Fragment,
  Suspense,
  unstable_SuspenseList as SuspenseList,
  useState,
} from 'react';

function SuspenseTree(): React.Node {
  return (
    <Fragment>
      <h1>Suspense</h1>
      <h4>Primary to Fallback Cycle</h4>
      <PrimaryFallbackTest initialSuspend={false} />
      <h4>Fallback to Primary Cycle</h4>
      <PrimaryFallbackTest initialSuspend={true} />
      <NestedSuspenseTest />
      <SuspenseListTest />
      <EmptySuspense />
    </Fragment>
  );
}

function EmptySuspense() {
  return <Suspense />;
}

// $FlowFixMe[missing-local-annot]
function PrimaryFallbackTest({initialSuspend}) {
  const [suspend, setSuspend] = useState(initialSuspend);
  const fallbackStep = useTestSequence('fallback', Fallback1, Fallback2);
  const primaryStep = useTestSequence('primary', Primary1, Primary2);
  return (
    <Fragment>
      <label>
        <input
          checked={suspend}
          onChange={e => setSuspend(e.target.checked)}
          type="checkbox"
        />
        Suspend
      </label>
      <br />
      <Suspense fallback={fallbackStep}>
        {suspend ? <Never /> : primaryStep}
      </Suspense>
    </Fragment>
  );
}

function useTestSequence(label: string, T1: any => any, T2: any => any) {
  const [step, setStep] = useState(0);
  const next: $FlowFixMe = (
    <button onClick={() => setStep(s => (s + 1) % allSteps.length)}>
      next {label} content
    </button>
  );
  const allSteps: $FlowFixMe = [
    <Fragment>{next}</Fragment>,
    <Fragment>
      {next} <T1 prop={step}>mount</T1>
    </Fragment>,
    <Fragment>
      {next} <T1 prop={step}>update</T1>
    </Fragment>,
    <Fragment>
      {next} <T2 prop={step}>several</T2> <T1 prop={step}>different</T1>{' '}
      <T2 prop={step}>children</T2>
    </Fragment>,
    <Fragment>
      {next} <T2 prop={step}>goodbye</T2>
    </Fragment>,
  ];
  return allSteps[step];
}

function NestedSuspenseTest() {
  return (
    <Fragment>
      <h3>Nested Suspense</h3>
      <Suspense fallback={<Fallback1>Loading outer</Fallback1>}>
        <Parent />
      </Suspense>
    </Fragment>
  );
}

function Parent() {
  return (
    <div>
      <Suspense fallback={<Fallback1>Loading inner 1</Fallback1>}>
        <Primary1>Hello</Primary1>
      </Suspense>{' '}
      <Suspense fallback={<Fallback2>Loading inner 2</Fallback2>}>
        <Primary2>World</Primary2>
      </Suspense>
      <br />
      <Suspense fallback={<Fallback1>This will never load</Fallback1>}>
        <Never />
      </Suspense>
      <br />
      <b>
        <LoadLater />
      </b>
    </div>
  );
}

function SuspenseListTest() {
  return (
    <>
      <h1>SuspenseList</h1>
      <SuspenseList revealOrder="forwards" tail="collapsed">
        <div>
          <Suspense fallback={<Fallback1>Loading 1</Fallback1>}>
            <Primary1>Hello</Primary1>
          </Suspense>
        </div>
        <div>
          <LoadLater />
        </div>
        <div>
          <Suspense fallback={<Fallback2>Loading 2</Fallback2>}>
            <Primary2>World</Primary2>
          </Suspense>
        </div>
      </SuspenseList>
    </>
  );
}

function LoadLater() {
  const [loadChild, setLoadChild] = useState(false);
  return (
    <Suspense
      fallback={
        <Fallback1 onClick={() => setLoadChild(true)}>Click to load</Fallback1>
      }>
      {loadChild ? (
        <Primary1 onClick={() => setLoadChild(false)}>
          Loaded! Click to suspend again.
        </Primary1>
      ) : (
        <Never />
      )}
    </Suspense>
  );
}

function Never() {
  throw new Promise(resolve => {});
}

function Fallback1({prop, ...rest}: any) {
  return <span {...rest} />;
}

function Fallback2({prop, ...rest}: any) {
  return <span {...rest} />;
}

function Primary1({prop, ...rest}: any) {
  return <span {...rest} />;
}

function Primary2({prop, ...rest}: any) {
  return <span {...rest} />;
}

export default SuspenseTree;
