import './status.css';

import { Menu, MenuDivider, MenuHeader, MenuItem } from '@szhsin/react-menu';
import debounce from 'just-debounce-it';
import pRetry from 'p-retry';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { InView } from 'react-intersection-observer';
import { matchPath, useParams, useSearchParams } from 'react-router-dom';
import { useDebouncedCallback } from 'use-debounce';
import { useSnapshot } from 'valtio';

import Avatar from '../components/avatar';
import Icon from '../components/icon';
import Link from '../components/link';
import Loader from '../components/loader';
import MediaModal from '../components/media-modal';
import NameText from '../components/name-text';
import RelativeTime from '../components/relative-time';
import Status from '../components/status';
import { api } from '../utils/api';
import htmlContentLength from '../utils/html-content-length';
import shortenNumber from '../utils/shorten-number';
import states, {
  getStatus,
  saveStatus,
  statusKey,
  threadifyStatus,
} from '../utils/states';
import statusPeek from '../utils/status-peek';
import { getCurrentAccount } from '../utils/store-utils';
import useScroll from '../utils/useScroll';
import useTitle from '../utils/useTitle';

const LIMIT = 40;
const THREAD_LIMIT = 20;

let cachedRepliesToggle = {};
let cachedStatusesMap = {};
function resetScrollPosition(id) {
  delete cachedStatusesMap[id];
  delete states.scrollPositions[id];
}

function StatusPage() {
  const { id, ...params } = useParams();
  const { masto, instance } = api({ instance: params.instance });
  const [searchParams, setSearchParams] = useSearchParams();
  const mediaParam = searchParams.get('media');
  const mediaOnlyParam = searchParams.get('media-only');
  const mediaIndex = parseInt(mediaParam || mediaOnlyParam, 10);
  let showMedia = mediaIndex > 0;
  const mediaStatusID = searchParams.get('mediaStatusID');
  const mediaStatus = getStatus(mediaStatusID, instance);
  if (mediaStatusID && !mediaStatus) {
    showMedia = false;
  }
  const showMediaOnly = showMedia && !!mediaOnlyParam;

  const sKey = statusKey(id, instance);
  const [heroStatus, setHeroStatus] = useState(states.statuses[sKey]);

  const closeLink = useMemo(() => {
    const { prevLocation } = states;
    const pathname =
      (prevLocation?.pathname || '') + (prevLocation?.search || '');
    const matchStatusPath =
      matchPath('/:instance/s/:id', pathname) || matchPath('/s/:id', pathname);
    if (!pathname || matchStatusPath) {
      return '/';
    }
    return pathname;
  }, []);

  useEffect(() => {
    if (!heroStatus && showMedia) {
      (async () => {
        try {
          const status = await masto.v1.statuses.fetch(id);
          saveStatus(status, instance);
          setHeroStatus(status);
        } catch (err) {
          console.error(err);
          alert('Unable to load status.');
          location.hash = closeLink;
        }
      })();
    }
  }, [showMedia]);

  const mediaAttachments = mediaStatusID
    ? mediaStatus?.mediaAttachments
    : heroStatus?.mediaAttachments;

  return (
    <div class="deck-backdrop">
      {showMedia ? (
        mediaAttachments?.length ? (
          <MediaModal
            mediaAttachments={mediaAttachments}
            statusID={mediaStatusID || id}
            instance={instance}
            index={mediaIndex - 1}
            onClose={() => {
              if (showMediaOnly) {
                location.hash = closeLink;
              } else {
                searchParams.delete('media');
                searchParams.delete('mediaStatusID');
                setSearchParams(searchParams);
              }
            }}
          />
        ) : (
          <div class="media-modal-container loading">
            <Loader abrupt />
          </div>
        )
      ) : (
        <Link to={closeLink} />
      )}
      {!showMediaOnly && <StatusThread closeLink={closeLink} />}
    </div>
  );
}

function StatusThread({ closeLink = '/' }) {
  const { id, ...params } = useParams();
  const [searchParams, setSearchParams] = useSearchParams();
  const mediaParam = searchParams.get('media');
  const showMedia = parseInt(mediaParam, 10) > 0;
  const { masto, instance } = api({ instance: params.instance });
  const {
    masto: currentMasto,
    instance: currentInstance,
    authenticated,
  } = api();
  const sameInstance = instance === currentInstance;
  const snapStates = useSnapshot(states);
  const [statuses, setStatuses] = useState([]);
  const [uiState, setUIState] = useState('default');
  const heroStatusRef = useRef();
  const sKey = statusKey(id, instance);

  const scrollableRef = useRef();
  useEffect(() => {
    scrollableRef.current?.focus();
  }, []);
  useEffect(() => {
    const onScroll = debounce(() => {
      // console.log('onScroll');
      if (!scrollableRef.current) return;
      const { scrollTop } = scrollableRef.current;
      if (uiState !== 'loading') {
        states.scrollPositions[id] = scrollTop;
      }
    }, 50);
    scrollableRef.current?.addEventListener('scroll', onScroll, {
      passive: true,
    });
    onScroll();
    return () => {
      onScroll.cancel();
      scrollableRef.current?.removeEventListener('scroll', onScroll);
    };
  }, [id, uiState !== 'loading']);

  const scrollOffsets = useRef();
  const initContext = ({ reloadHero } = {}) => {
    console.debug('initContext', id);
    setUIState('loading');
    let heroTimer;

    const cachedStatuses = cachedStatusesMap[id];
    if (cachedStatuses) {
      // Case 1: It's cached, let's restore them to make it snappy
      const reallyCachedStatuses = cachedStatuses.filter(
        (s) => states.statuses[sKey],
        // Some are not cached in the global state, so we need to filter them out
      );
      setStatuses(reallyCachedStatuses);
    } else {
      // const heroIndex = statuses.findIndex((s) => s.id === id);
      // if (heroIndex !== -1) {
      //   // Case 2: It's in current statuses. Slice off all descendant statuses after the hero status to be safe
      //   const slicedStatuses = statuses.slice(0, heroIndex + 1);
      //   setStatuses(slicedStatuses);
      // } else {
      // Case 3: Not cached and not in statuses, let's start from scratch
      setStatuses([{ id }]);
      // }
    }

    (async () => {
      const heroFetch = () =>
        pRetry(() => masto.v1.statuses.fetch(id), {
          retries: 4,
        });
      const contextFetch = pRetry(() => masto.v1.statuses.fetchContext(id), {
        retries: 8,
      });

      const hasStatus = !!snapStates.statuses[sKey];
      let heroStatus = snapStates.statuses[sKey];
      if (hasStatus && !reloadHero) {
        console.debug('Hero status is cached');
      } else {
        try {
          heroStatus = await heroFetch();
          saveStatus(heroStatus, instance);
          // Give time for context to appear
          await new Promise((resolve) => {
            setTimeout(resolve, 100);
          });
        } catch (e) {
          console.error(e);
          setUIState('error');
          return;
        }
      }

      try {
        const context = await contextFetch;
        const { ancestors, descendants } = context;

        ancestors.forEach((status) => {
          saveStatus(status, instance, {
            skipThreading: true,
          });
        });
        const ancestorsIsThread = ancestors.every(
          (s) => s.account.id === heroStatus.account.id,
        );
        const nestedDescendants = [];
        descendants.forEach((status) => {
          saveStatus(status, instance, {
            skipThreading: true,
          });
          if (status.inReplyToAccountId === status.account.id) {
            // If replying to self, it's part of the thread, level 1
            nestedDescendants.push(status);
          } else if (status.inReplyToId === heroStatus.id) {
            // If replying to the hero status, it's a reply, level 1
            nestedDescendants.push(status);
          } else {
            // If replying to someone else, it's a reply to a reply, level 2
            const parent = descendants.find((s) => s.id === status.inReplyToId);
            if (parent) {
              if (!parent.__replies) {
                parent.__replies = [];
              }
              parent.__replies.push(status);
            } else {
              // If no parent, something is wrong
              console.warn('No parent found for', status);
            }
          }
        });

        console.log({ ancestors, descendants, nestedDescendants });

        function expandReplies(_replies) {
          return _replies?.map((_r) => ({
            id: _r.id,
            account: _r.account,
            repliesCount: _r.repliesCount,
            content: _r.content,
            replies: expandReplies(_r.__replies),
          }));
        }

        const allStatuses = [
          ...ancestors.map((s) => ({
            id: s.id,
            ancestor: true,
            isThread: ancestorsIsThread,
            accountID: s.account.id,
            repliesCount: s.repliesCount,
          })),
          { id, accountID: heroStatus.account.id },
          ...nestedDescendants.map((s) => ({
            id: s.id,
            accountID: s.account.id,
            descendant: true,
            thread: s.account.id === heroStatus.account.id,
            replies: expandReplies(s.__replies),
          })),
        ];

        setUIState('default');
        scrollOffsets.current = {
          offsetTop: heroStatusRef.current?.offsetTop,
          scrollTop: scrollableRef.current?.scrollTop,
        };
        console.log({ allStatuses });
        setStatuses(allStatuses);
        cachedStatusesMap[id] = allStatuses;

        // Let's threadify this one
        // Note that all non-hero statuses will trigger saveStatus which will threadify them too
        // By right, at this point, all descendant statuses should be cached
        threadifyStatus(heroStatus, instance);
      } catch (e) {
        console.error(e);
        setUIState('error');
      }
    })();

    return () => {
      clearTimeout(heroTimer);
    };
  };

  useEffect(initContext, [id, masto]);
  useEffect(() => {
    if (!statuses.length) return;
    console.debug('STATUSES', statuses);
    const scrollPosition = states.scrollPositions[id];
    console.debug('scrollPosition', scrollPosition);
    if (!!scrollPosition) {
      console.debug('Case 1', {
        id,
        scrollPosition,
      });
      scrollableRef.current.scrollTop = scrollPosition;
    } else if (scrollOffsets.current) {
      const newScrollOffsets = {
        offsetTop: heroStatusRef.current?.offsetTop,
        scrollTop: scrollableRef.current?.scrollTop,
      };
      const newScrollTop =
        newScrollOffsets.offsetTop -
        scrollOffsets.current.offsetTop +
        newScrollOffsets.scrollTop;
      console.debug('Case 2', {
        scrollOffsets: scrollOffsets.current,
        newScrollOffsets,
        newScrollTop,
        statuses: [...statuses],
      });
      scrollableRef.current.scrollTop = newScrollTop;
    } else if (statuses.length === 1) {
      console.debug('Case 3', {
        id,
      });
      scrollableRef.current.scrollTop = 0;
    }

    // RESET
    scrollOffsets.current = null;
  }, [statuses]);

  useEffect(() => {
    if (snapStates.reloadStatusPage <= 0) return;
    // Delete the cache for the context
    (async () => {
      try {
        const { instanceURL } = getCurrentAccount();
        const contextURL = `https://${instanceURL}/api/v1/statuses/${id}/context`;
        console.log('Clear cache', contextURL);
        const apiCache = await caches.open('api');
        await apiCache.delete(contextURL, { ignoreVary: true });

        return initContext({
          reloadHero: true,
        });
      } catch (e) {
        console.error(e);
      }
    })();
  }, [snapStates.reloadStatusPage]);

  useEffect(() => {
    return () => {
      // RESET
      states.scrollPositions = {};
      states.reloadStatusPage = 0;
      cachedStatusesMap = {};
      cachedRepliesToggle = {};
    };
  }, []);

  const heroStatus = snapStates.statuses[sKey] || snapStates.statuses[id];
  const heroDisplayName = useMemo(() => {
    // Remove shortcodes from display name
    if (!heroStatus) return '';
    const { account } = heroStatus;
    const div = document.createElement('div');
    div.innerHTML = account.displayName;
    return div.innerText.trim();
  }, [heroStatus]);
  const heroContentText = useMemo(() => {
    if (!heroStatus) return '';
    let text = statusPeek(heroStatus);
    if (text.length > 64) {
      // "The title should ideally be less than 64 characters in length"
      // https://www.w3.org/Provider/Style/TITLE.html
      text = text.slice(0, 64) + '…';
    }
    return text;
  }, [heroStatus]);
  useTitle(
    heroDisplayName && heroContentText
      ? `${heroDisplayName}: "${heroContentText}"`
      : 'Status',
    '/:instance?/s/:id',
  );

  const postInstance = useMemo(() => {
    if (!heroStatus) return;
    const { url } = heroStatus;
    if (!url) return;
    return new URL(url).hostname;
  }, [heroStatus]);
  const postSameInstance = useMemo(() => {
    if (!postInstance) return;
    return postInstance === instance;
  }, [postInstance, instance]);

  const [limit, setLimit] = useState(LIMIT);
  const showMore = useMemo(() => {
    // return number of statuses to show
    return statuses.length - limit;
  }, [statuses.length, limit]);

  const hasManyStatuses = statuses.length > THREAD_LIMIT;
  const hasDescendants = statuses.some((s) => s.descendant);
  const ancestors = statuses.filter((s) => s.ancestor);

  const [heroInView, setHeroInView] = useState(true);
  const onView = useDebouncedCallback(setHeroInView, 100);
  const heroPointer = useMemo(() => {
    // get top offset of heroStatus
    if (!heroStatusRef.current || heroInView) return null;
    const { top } = heroStatusRef.current.getBoundingClientRect();
    return top > 0 ? 'down' : 'up';
  }, [heroInView]);

  useHotkeys(
    'esc',
    () => {
      location.hash = closeLink;
    },
    {
      // If media is open, esc to close media first
      // Else close the status page
      enabled: !showMedia,
    },
  );
  // For backspace, will always close both media and status page
  useHotkeys('backspace', () => {
    location.hash = closeLink;
  });

  useHotkeys('j', () => {
    const activeStatus = document.activeElement.closest(
      '.status-link, .status-focus',
    );
    const activeStatusRect = activeStatus?.getBoundingClientRect();
    const allStatusLinks = Array.from(
      // Select all statuses except those inside collapsed details/summary
      // Hat-tip to @AmeliaBR@front-end.social
      // https://front-end.social/@AmeliaBR/109784776146144471
      scrollableRef.current.querySelectorAll(
        '.status-link:not(details:not([open]) > summary ~ *, details:not([open]) > summary ~ * *), .status-focus:not(details:not([open]) > summary ~ *, details:not([open]) > summary ~ * *)',
      ),
    );
    console.log({ allStatusLinks });
    if (
      activeStatus &&
      activeStatusRect.top < scrollableRef.current.clientHeight &&
      activeStatusRect.bottom > 0
    ) {
      const activeStatusIndex = allStatusLinks.indexOf(activeStatus);
      let nextStatus = allStatusLinks[activeStatusIndex + 1];
      if (nextStatus) {
        nextStatus.focus();
        nextStatus.scrollIntoViewIfNeeded?.();
      }
    } else {
      // If active status is not in viewport, get the topmost status-link in viewport
      const topmostStatusLink = allStatusLinks.find((statusLink) => {
        const statusLinkRect = statusLink.getBoundingClientRect();
        return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real
      });
      if (topmostStatusLink) {
        topmostStatusLink.focus();
        topmostStatusLink.scrollIntoViewIfNeeded?.();
      }
    }
  });

  useHotkeys('k', () => {
    const activeStatus = document.activeElement.closest(
      '.status-link, .status-focus',
    );
    const activeStatusRect = activeStatus?.getBoundingClientRect();
    const allStatusLinks = Array.from(
      scrollableRef.current.querySelectorAll(
        '.status-link:not(details:not([open]) > summary ~ *, details:not([open]) > summary ~ * *), .status-focus:not(details:not([open]) > summary ~ *, details:not([open]) > summary ~ * *)',
      ),
    );
    if (
      activeStatus &&
      activeStatusRect.top < scrollableRef.current.clientHeight &&
      activeStatusRect.bottom > 0
    ) {
      const activeStatusIndex = allStatusLinks.indexOf(activeStatus);
      let prevStatus = allStatusLinks[activeStatusIndex - 1];
      if (prevStatus) {
        prevStatus.focus();
        prevStatus.scrollIntoViewIfNeeded?.();
      }
    } else {
      // If active status is not in viewport, get the topmost status-link in viewport
      const topmostStatusLink = allStatusLinks.find((statusLink) => {
        const statusLinkRect = statusLink.getBoundingClientRect();
        return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real
      });
      if (topmostStatusLink) {
        topmostStatusLink.focus();
        topmostStatusLink.scrollIntoViewIfNeeded?.();
      }
    }
  });

  // NOTE: I'm not sure if 'x' is the best shortcut for this, might change it later
  // IDEA: x is for expand
  useHotkeys('x', () => {
    const activeStatus = document.activeElement.closest(
      '.status-link, .status-focus',
    );
    if (activeStatus) {
      const details = activeStatus.nextElementSibling;
      if (details && details.tagName.toLowerCase() === 'details') {
        details.open = !details.open;
      }
    }
  });

  const { nearReachStart } = useScroll({
    scrollableRef,
    distanceFromStartPx: 16,
  });

  const initialPageState = useRef(showMedia ? 'media+status' : 'status');

  return (
    <div
      tabIndex="-1"
      ref={scrollableRef}
      class={`status-deck deck contained ${
        statuses.length > 1 ? 'padded-bottom' : ''
      } ${initialPageState.current === 'status' ? 'slide-in' : ''}`}
    >
      <header
        class={`${heroInView ? 'inview' : ''} ${
          uiState === 'loading' ? 'loading' : ''
        }`}
        onDblClick={(e) => {
          // reload statuses
          states.reloadStatusPage++;
        }}
      >
        {/* <div>
            <Link class="button plain deck-close" href={closeLink}>
              <Icon icon="chevron-left" size="xl" />
            </Link>
          </div> */}
        <div class="header-grid header-grid-2">
          <h1>
            {!heroInView && heroStatus && uiState !== 'loading' ? (
              <>
                <span class="hero-heading">
                  <NameText
                    account={heroStatus.account}
                    instance={instance}
                    showAvatar
                    short
                  />{' '}
                  <span class="insignificant">
                    &bull;{' '}
                    <RelativeTime
                      datetime={heroStatus.createdAt}
                      format="micro"
                    />
                  </span>
                </span>{' '}
                <button
                  type="button"
                  class="ancestors-indicator light small"
                  onClick={(e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    heroStatusRef.current.scrollIntoView({
                      behavior: 'smooth',
                      block: 'start',
                    });
                  }}
                >
                  <Icon
                    icon={heroPointer === 'down' ? 'arrow-down' : 'arrow-up'}
                  />
                </button>
              </>
            ) : (
              <>
                Status{' '}
                <button
                  type="button"
                  class="ancestors-indicator light small"
                  onClick={(e) => {
                    // Scroll to top
                    e.preventDefault();
                    e.stopPropagation();
                    scrollableRef.current.scrollTo({
                      top: 0,
                      behavior: 'smooth',
                    });
                  }}
                  hidden={!ancestors.length || nearReachStart}
                >
                  <Icon icon="arrow-up" />
                  <Icon icon="comment" />{' '}
                  <span class="insignificant">
                    {shortenNumber(ancestors.length)}
                  </span>
                </button>
              </>
            )}
          </h1>
          <div class="header-side">
            <Menu
              align="end"
              portal={{
                // Need this, else the menu click will cause scroll jump
                target: scrollableRef.current,
              }}
              menuButton={
                <button type="button" class="button plain4">
                  <Icon icon="more" alt="Actions" size="xl" />
                </button>
              }
            >
              <MenuItem
                disabled={uiState === 'loading'}
                onClick={() => {
                  states.reloadStatusPage++;
                }}
              >
                <Icon icon="refresh" />
                <span>Refresh</span>
              </MenuItem>
              <MenuItem
                onClick={() => {
                  // Click all buttons with class .spoiler but not .spoiling
                  const buttons = Array.from(
                    scrollableRef.current.querySelectorAll(
                      'button.spoiler:not(.spoiling)',
                    ),
                  );
                  buttons.forEach((button) => {
                    button.click();
                  });
                }}
              >
                <Icon icon="eye-open" /> <span>Show all sensitive content</span>
              </MenuItem>
              <MenuDivider />
              <MenuHeader className="plain">Experimental</MenuHeader>
              <MenuItem
                disabled={postSameInstance}
                onClick={() => {
                  const statusURL = getInstanceStatusURL(heroStatus.url);
                  if (statusURL) {
                    location.hash = statusURL;
                  } else {
                    alert('Unable to switch');
                  }
                }}
              >
                <Icon icon="transfer" />
                <small class="menu-double-lines">
                  Switch to post's instance (<b>{postInstance}</b>)
                </small>
              </MenuItem>
            </Menu>
            <Link class="button plain deck-close" to={closeLink}>
              <Icon icon="x" size="xl" />
            </Link>
          </div>
        </div>
      </header>
      {!!statuses.length && heroStatus ? (
        <ul
          class={`timeline flat contextual grow ${
            uiState === 'loading' ? 'loading' : ''
          }`}
        >
          {statuses.slice(0, limit).map((status) => {
            const {
              id: statusID,
              ancestor,
              isThread,
              descendant,
              thread,
              replies,
              repliesCount,
            } = status;
            const isHero = statusID === id;
            return (
              <li
                key={statusID}
                ref={isHero ? heroStatusRef : null}
                class={`${ancestor ? 'ancestor' : ''} ${
                  descendant ? 'descendant' : ''
                } ${thread ? 'thread' : ''} ${isHero ? 'hero' : ''}`}
              >
                {isHero ? (
                  <>
                    <InView
                      threshold={0.1}
                      onChange={onView}
                      class="status-focus"
                      tabIndex={0}
                    >
                      <Status
                        statusID={statusID}
                        instance={instance}
                        withinContext
                        size="l"
                        enableTranslate
                      />
                    </InView>
                    {uiState !== 'loading' && !authenticated ? (
                      <div class="post-status-banner">
                        <p>
                          You're not logged in. Interactions (reply, boost, etc)
                          are not possible.
                        </p>
                        <Link to="/login" class="button">
                          Log in
                        </Link>
                      </div>
                    ) : (
                      !sameInstance && (
                        <div class="post-status-banner">
                          <p>
                            This post is from another instance (
                            <b>{instance}</b>). Interactions (reply, boost, etc)
                            are not possible.
                          </p>
                          <button
                            type="button"
                            disabled={uiState === 'loading'}
                            onClick={() => {
                              setUIState('loading');
                              (async () => {
                                try {
                                  const results = await currentMasto.v2.search({
                                    q: heroStatus.url,
                                    type: 'statuses',
                                    resolve: true,
                                    limit: 1,
                                  });
                                  if (results.statuses.length) {
                                    const status = results.statuses[0];
                                    location.hash = currentInstance
                                      ? `/${currentInstance}/s/${status.id}`
                                      : `/s/${status.id}`;
                                  } else {
                                    throw new Error('No results');
                                  }
                                } catch (e) {
                                  setUIState('default');
                                  alert('Error: ' + e);
                                  console.error(e);
                                }
                              })();
                            }}
                          >
                            <Icon icon="transfer" /> Switch to my instance to
                            enable interactions
                          </button>
                        </div>
                      )
                    )}
                  </>
                ) : (
                  <Link
                    class="status-link"
                    to={
                      instance ? `/${instance}/s/${statusID}` : `/s/${statusID}`
                    }
                    onClick={() => {
                      resetScrollPosition(statusID);
                    }}
                  >
                    <Status
                      statusID={statusID}
                      instance={instance}
                      withinContext
                      size={thread || ancestor ? 'm' : 's'}
                      enableTranslate
                      onMediaClick={(e, i) => {
                        e.preventDefault();
                        e.stopPropagation();
                        setSearchParams({
                          media: i + 1,
                          mediaStatusID: statusID,
                        });
                      }}
                    />
                    {ancestor && isThread && repliesCount > 1 && (
                      <div class="replies-link">
                        <Icon icon="comment" />{' '}
                        <span title={repliesCount}>
                          {shortenNumber(repliesCount)}
                        </span>
                      </div>
                    )}{' '}
                    {/* {replies?.length > LIMIT && (
                        <div class="replies-link">
                          <Icon icon="comment" />{' '}
                          <span title={replies.length}>
                            {shortenNumber(replies.length)}
                          </span>
                        </div>
                      )} */}
                  </Link>
                )}
                {descendant && replies?.length > 0 && (
                  <SubComments
                    instance={instance}
                    hasManyStatuses={hasManyStatuses}
                    replies={replies}
                    hasParentThread={thread}
                    level={1}
                  />
                )}
                {uiState === 'loading' &&
                  isHero &&
                  !!heroStatus?.repliesCount &&
                  !hasDescendants && (
                    <div class="status-loading">
                      <Loader />
                    </div>
                  )}
                {uiState === 'error' &&
                  isHero &&
                  !!heroStatus?.repliesCount &&
                  !hasDescendants && (
                    <div class="status-error">
                      Unable to load replies.
                      <br />
                      <button
                        type="button"
                        class="plain"
                        onClick={() => {
                          states.reloadStatusPage++;
                        }}
                      >
                        Try again
                      </button>
                    </div>
                  )}
              </li>
            );
          })}
          {showMore > 0 && (
            <li>
              <button
                type="button"
                class="plain block"
                disabled={uiState === 'loading'}
                onClick={() => setLimit((l) => l + LIMIT)}
                style={{ marginBlockEnd: '6em' }}
              >
                Show more&hellip;{' '}
                <span class="tag">
                  {showMore > LIMIT ? `${LIMIT}+` : showMore}
                </span>
              </button>
            </li>
          )}
        </ul>
      ) : (
        <>
          {uiState === 'loading' && (
            <ul class="timeline flat contextual grow loading">
              <li>
                <Status skeleton size="l" />
              </li>
            </ul>
          )}
          {uiState === 'error' && (
            <p class="ui-state">
              Unable to load status
              <br />
              <br />
              <button
                type="button"
                onClick={() => {
                  states.reloadStatusPage++;
                }}
              >
                Try again
              </button>
            </p>
          )}
        </>
      )}
    </div>
  );
}

function SubComments({
  hasManyStatuses,
  replies,
  instance,
  hasParentThread,
  level,
}) {
  const [searchParams, setSearchParams] = useSearchParams();
  // Set isBrief = true:
  // - if less than or 2 replies
  // - if replies have no sub-replies
  // - if total number of characters of content from replies is less than 500
  let isBrief = false;
  if (replies.length <= 2) {
    const containsSubReplies = replies.some(
      (r) => r.repliesCount > 0 || r.replies?.length > 0,
    );
    if (!containsSubReplies) {
      let totalLength = replies.reduce((acc, reply) => {
        const { content } = reply;
        const length = htmlContentLength(content);
        return acc + length;
      }, 0);
      isBrief = totalLength < 500;
    }
  }

  // Total comments count, including sub-replies
  const diveDeep = (replies) => {
    return replies.reduce((acc, reply) => {
      const { repliesCount, replies } = reply;
      const count = replies?.length || repliesCount;
      return acc + count + diveDeep(replies || []);
    }, 0);
  };
  const totalComments = replies.length + diveDeep(replies);
  const sameCount = replies.length === totalComments;

  // Get the first 3 accounts, unique by id
  const accounts = replies
    .map((r) => r.account)
    .filter((a, i, arr) => arr.findIndex((b) => b.id === a.id) === i)
    .slice(0, 3);

  const open =
    (!hasParentThread || replies.length === 1) && (isBrief || !hasManyStatuses);
  const openBefore = cachedRepliesToggle[replies[0].id];

  return (
    <details
      class="replies"
      open={openBefore || open}
      onToggle={(e) => {
        const { open } = e.target;
        // use first reply as ID
        cachedRepliesToggle[replies[0].id] = open;
      }}
      style={{
        '--comments-level': level,
      }}
      data-comments-level={level}
      data-comments-level-overflow={level > 4}
    >
      <summary class="replies-summary" hidden={open}>
        <span class="avatars">
          {accounts.map((a) => (
            <Avatar
              key={a.id}
              url={a.avatarStatic}
              title={`${a.displayName} @${a.username}`}
              squircle={a?.bot}
            />
          ))}
        </span>
        <span>
          <span title={replies.length}>{shortenNumber(replies.length)}</span>{' '}
          repl
          {replies.length === 1 ? 'y' : 'ies'}
        </span>
        {!sameCount && totalComments > 1 && (
          <>
            {' '}
            &middot;{' '}
            <span>
              <span title={totalComments}>{shortenNumber(totalComments)}</span>{' '}
              comment
              {totalComments === 1 ? '' : 's'}
            </span>
          </>
        )}
      </summary>
      <ul>
        {replies.map((r) => (
          <li key={r.id}>
            <Link
              class="status-link"
              to={instance ? `/${instance}/s/${r.id}` : `/s/${r.id}`}
              onClick={() => {
                resetScrollPosition(r.id);
              }}
            >
              <Status
                statusID={r.id}
                instance={instance}
                withinContext
                size="s"
                enableTranslate
                onMediaClick={(e, i) => {
                  e.preventDefault();
                  e.stopPropagation();
                  setSearchParams({
                    media: i + 1,
                    mediaStatusID: r.id,
                  });
                }}
              />
              {!r.replies?.length && r.repliesCount > 0 && (
                <div class="replies-link">
                  <Icon icon="comment" />{' '}
                  <span title={r.repliesCount}>
                    {shortenNumber(r.repliesCount)}
                  </span>
                </div>
              )}
            </Link>
            {r.replies?.length && (
              <SubComments
                instance={instance}
                hasManyStatuses={hasManyStatuses}
                replies={r.replies}
                level={level + 1}
              />
            )}
          </li>
        ))}
      </ul>
    </details>
  );
}

const statusRegex = /\/@([^@\/]+)@?([^\/]+)?\/([^\/]+)\/?$/i;
const statusNoteRegex = /\/notes\/([^\/]+)\/?$/i;
function getInstanceStatusURL(url) {
  // Regex /:username/:id, where username = @username or @username@domain, id = anything
  const { hostname, pathname } = new URL(url);
  const [, username, domain, id] = pathname.match(statusRegex) || [];
  if (id) {
    return `/${hostname}/s/${id}`;
  }
  const [, noteId] = pathname.match(statusNoteRegex) || [];
  if (noteId) {
    return `/${hostname}/s/${noteId}`;
  }
}

export default StatusPage;