import type { LinkType } from '@/components/LinkButton'
import { getIcon } from '@/views/ChannelButton'
import type { NonIdealStateProps } from '@blueprintjs/core'
import {
  ButtonGroup,
  Divider,
  EntityTitle,
  NonIdealState,
} from '@blueprintjs/core'
import { css, cx } from '@emotion/css'
import type { ActionLogSimple, Channel } from '@inject/graphql/fragment-types'
import {
  Fragment,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  type FC,
} from 'react'
import InjectMessage from '../InjectMessage'
import ScrollDownButton from './ScrollDownButton'
import SeeAllUnreadButton from './SeeAllUnreadButton'

const instructorView = css`
  height: calc(100vh - 48px - 0.25rem);
  /* THIS CALCULATION IS DONE DUE TO HEIGHT OF TEAM TABS (/views/InstructorView/TeamTabs.tsx) 
  48px - height, 0.25rem - margin
  */
`
const view = css`
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding-top: 1rem;
  max-height: 100vh;
`

const bottom = css`
  position: absolute;
  bottom: 1rem;
  right: 1rem;
  display: flex;
  justify-content: flex-end;
`

const unreadLogsDivider = css`
  display: flex;
  gap: 0.5rem;
  margin: 0.5rem 2rem;
`

const divider = css`
  flex: 1;
  align-self: center;
`

const scrollable = css`
  flex: 1;
  overflow-y: auto;
  margin-bottom: 4rem;
`

const ListRenderer: FC<{
  actionLogs: ActionLogSimple[]
  exerciseId: string
  teamId: string
  channel: Channel
  inInstructor: boolean
  getFullscreenLink: (actionLogId: string) => LinkType
  scrollToLast?: boolean
  noDataProps?: NonIdealStateProps
}> = ({
  actionLogs,
  exerciseId,
  teamId,
  channel,
  inInstructor,
  getFullscreenLink,
  scrollToLast,
  noDataProps,
}) => {
  const actionLogsLengthPrev = useRef<number>(actionLogs.length)

  const getFirstUnreadId = useCallback(
    () => actionLogs.find(actionLog => !actionLog.readReceipt)?.id,
    [actionLogs]
  )
  /*
   * not using useMemo to have control over when to update the state
   * (see useScroll.ts)
   */
  const [firstUnreadId, setFirstUnreadId] = useState<string | undefined>(
    getFirstUnreadId()
  )

  const lastUnreadId = useMemo(() => {
    const last = actionLogs.at(-1)
    return last?.readReceipt ? undefined : last?.id
  }, [actionLogs])

  const unreadLogsStartRef = useRef<HTMLDivElement | null>(null)
  const lastUnreadLogRef = useRef<HTMLDivElement | null>(null)

  // scroll to bottom when rendered, or when changing teams or channels
  useEffect(() => {
    const ref = document.getElementById('scrollable') as HTMLDivElement
    if (ref) {
      ref.scrollTo({ top: ref.scrollHeight, behavior: 'instant' })
    }
  }, [actionLogs])

  /*
   * Logs with readReceipt === undefined should be rendered as unread
   * the whole time the same channel is open for the same teams.
   * However, actionLog.readReceipt will update as soon as the InjectMessage
   * is rendered.
   *
   * The following effects prevent the update of actionLog.readReceipt
   * from rendering the actionLog as read.
   */

  useLayoutEffect(() => {
    setFirstUnreadId(getFirstUnreadId())
    // do not reset when actionLogs change
  }, [channel.id, teamId])

  useEffect(() => {
    if (actionLogs.length !== actionLogsLengthPrev.current) {
      if (firstUnreadId === undefined) setFirstUnreadId(getFirstUnreadId())

      if (scrollToLast) {
        lastUnreadLogRef.current?.scrollIntoView({
          behavior: 'smooth',
          inline: 'nearest',
        })
      }
    }
    actionLogsLengthPrev.current = actionLogs.length
  }, [actionLogs, firstUnreadId, getFirstUnreadId, scrollToLast])

  return (
    <div className={cx(view, { [instructorView]: inInstructor })} id='channel'>
      <>
        <EntityTitle
          title={channel.displayName}
          subtitle={channel.description ? channel.description : undefined}
          icon={getIcon(channel.type)}
        />
        <Divider />
      </>

      {actionLogs.length == 0 ? (
        <NonIdealState
          icon='low-voltage-pole'
          title='No injects found'
          description='There are no injects in this channel yet'
          className={css`
            height: 80vh;
            width: 100%;
          `}
          {...noDataProps}
        />
      ) : (
        <div className={scrollable} id='scrollable'>
          {actionLogs.map(actionLog => (
            <Fragment key={actionLog.id}>
              {/*
               * causes problems with scrolling if the last unread is also
               * the first, so I decided not to render the divider at all
               * in this case
               */}
              {actionLog.id === firstUnreadId && (
                <div ref={unreadLogsStartRef} className={unreadLogsDivider}>
                  <Divider className={divider} />
                  <div>Unread:</div>
                  <Divider className={divider} />
                </div>
              )}
              {lastUnreadId === actionLog.id && <div ref={lastUnreadLogRef} />}
              <InjectMessage
                exerciseId={exerciseId}
                teamId={teamId}
                actionLog={actionLog}
                fullscreenLink={getFullscreenLink(actionLog.id)}
                inInstructor={inInstructor}
              />
            </Fragment>
          ))}
        </div>
      )}

      <div className={bottom}>
        <ButtonGroup>
          <ScrollDownButton
            actionLogsLength={actionLogs.length}
            teamId={teamId}
            channelId={channel.id}
          />
          <SeeAllUnreadButton
            firstUnreadId={firstUnreadId}
            unreadLogsStartRef={unreadLogsStartRef}
          />
        </ButtonGroup>
      </div>
    </div>
  )
}

export default ListRenderer
