import styled from '@emotion/styled';
import clsx from 'clsx';
import { observer } from 'mobx-react-lite';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import { ModalTypes } from '@/components/modal/constants/ModalTypes';
import { useModalContext } from '@/context/ModalContext';
import useClip from '@/hooks/useClip';
import {
  CheckboxIcon,
  CheckboxOutlineIcon,
  FeedbackIcon,
  MoreVerticalIcon,
  ThumbsDownIcon,
  ThumbsUpIcon,
} from '@/icons';
import { Clip, isDisliked, isLiked, isTimedOut } from '@/state/clipStore';
import { encodeTimeFormat } from '@/utils/utils';

import Button, { ButtonSize, ButtonVariant } from '../button/Button';
import {
  ContextMenuItem,
  ContextMenuTrigger,
} from '../contextMenu/ContextMenu';
import SpinnerSVG from '../svg/SpinnerSVG';
import { EditMode } from './EditModeContext';
import GenerateCountContext from './GenerateContext';
import PreviewClipContext from './PreviewClipContext';
import {
  GlassModule,
  GlassModuleBody,
  GlassModuleHeader,
  SongImage,
} from './components';
import { getEditType } from './getEditType';
import useEditAncestryQuery from './queryHooks/useEditAncestryQuery';
import { Edit } from './types';

const EditWrapper = styled.div<{
  active: boolean;
  isLoading: boolean;
  disliked?: boolean;
}>`
  position: relative;
  background-color: ${({ active }) =>
    active ? 'rgba(255,255,255,0.15)' : 'rgba(255,255,255,0.05)'};
  border-radius: 5px;
  padding: 5px;
  display: grid;
  padding-right: 10px;
  min-width: 0;
  grid-template-columns: 48px 1fr auto;
  gap: 15px;
  height: 80px;
  flex-shrink: 0;
  transition: background-color 0.2s;
  overflow: hidden;
  opacity: ${({ disliked }) => (disliked ? 0.25 : 1)};
  border: 2px solid
    ${({ active }) => (active ? 'rgba(255,255,255,1)' : 'transparent')};

  &:hover {
    ${({ isLoading, active }) =>
      isLoading
        ? ''
        : `
    cursor: pointer;
    background-color: ${active ? '' : 'rgba(255, 255, 255, 0.15)'};
    `}
  }
`;

const EditGhost = styled(EditWrapper)`
  display: flex;
  background-color: transparent;
  border: 2px solid rgba(255, 255, 255, 0.2);
  justify-content: center;
  align-items: center;
  flex-direction: column;
  gap: 5px;
`;

const EditMeta = styled.div`
  min-height: 67px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
`;

const EditTitle = styled.h4`
  font-size: 16px;
  color: rgba(255, 255, 255, 0.4);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  > * {
    margin-right: 7px;
  }
`;

const EditTitleMeta = styled.span`
  display: inline;
  font-weight: 300;
  font-size: 14px;
`;

const EditTitleMain = styled.span`
  color: rgba(255, 255, 255, 1);
  display: inline;
`;

const EditTags = styled.p`
  font-size: 12px;
  opacity: 0.4;
  max-height: 36px;
  overflow: hidden;
  text-overflow: ellipsis;
`;

const EditControls = styled.div`
  min-height: 67px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
`;

const EditStatus = styled.div`
  font-family: 'Input Sans', monospace;
  text-transform: uppercase;
  font-size: 10px;
  opacity: 0.5;
`;

const getUserFacingStatus = (status?: string) => {
  if (!status) return null;
  if (status === 'streaming') return 'Generating';
  return status.charAt(0).toUpperCase() + status.slice(1);
};

export const getEditClipTitle = (clip: Clip) => {
  const editType = getEditType(clip);
  if (editType === 'replace') return 'Replacement';
  if (editType === 'extend') return 'Extension';
  return 'Edit';
};

export const getEditModeTitle = (mode: EditMode) => {
  if (mode === 'replace') return 'Replacement';
  if (mode === 'extend') return 'Extension';
  return 'Edit';
};

// if we unmount and remount an edit, we can trust that its image etc is cached.
const displayedEditIDs: { [key: string]: true } = {};

const EditDisplay = observer(
  ({
    edit,
    showLiked,
    showDisliked,
    showUnrated,
  }: {
    edit: Edit;
    showLiked: boolean;
    showDisliked: boolean;
    showUnrated: boolean;
  }) => {
    const { clip: latestClipReference } = useClip(edit.clipId);
    const initialClipReference = edit.clip;
    const clip = latestClipReference || initialClipReference;

    const shouldDisplay = useMemo(() => {
      if (clip && showLiked && isLiked(clip)) return true;
      if (clip && showDisliked && isDisliked(clip)) return true;
      if (clip && showUnrated && !isLiked(clip) && !isDisliked(clip))
        return true;
      return false;
    }, [showLiked, showDisliked, showUnrated, clip]);

    const { previewClipId, setPreviewEdit } = useContext(PreviewClipContext);
    const { clips } = useStores();
    const { openModalWithData } = useModalContext();
    const [hasDisplayed, setHasDisplayed] = useState(
      displayedEditIDs[edit.clipId]
    );

    const [hasBeenPlayed, setHasBeenPlayed] = useState(false);

    const timeReadout = useMemo(() => {
      if (!clip) return '';
      if (clip.metadata?.infill) {
        const relevantHistoryEntry = clip.metadata.history?.find(
          (e) =>
            (e as any).infill_start_s !== undefined &&
            (e as any).infill_end_s !== undefined
        );
        if (relevantHistoryEntry) {
          const infillStartSeconds = (relevantHistoryEntry as any)
            .infill_start_s;
          const infillEndSeconds = (relevantHistoryEntry as any).infill_end_s;
          const startStr =
            infillStartSeconds > 60
              ? encodeTimeFormat(infillStartSeconds)
              : `${infillStartSeconds.toFixed(0)}s`;
          const endStr =
            infillEndSeconds > 60
              ? encodeTimeFormat(infillEndSeconds)
              : `${infillEndSeconds.toFixed(0)}s`;

          return `${startStr} to ${endStr}`;
        }
      }
      if (clip.metadata?.task === 'extend') {
        const relevantHistoryEntry = clip.metadata.history?.find(
          (e) => (e as any).continue_at !== undefined
        );
        const continueAt = (relevantHistoryEntry as any)?.continue_at;
        const continueAtStr =
          continueAt > 60
            ? encodeTimeFormat(continueAt)
            : `${continueAt.toFixed(0)}s`;
        if (relevantHistoryEntry) {
          return `from ${continueAtStr}`;
        }
      }
      return '';
    }, [clip]);

    const handleClick = useCallback(() => {
      if (clip?.status !== 'complete') return;
      setPreviewEdit((prev) => (prev?.clipId === edit.clipId ? null : edit));
      if (previewClipId !== edit.clipId) {
        const playCount = clip.play_count || 0;
        const baseSampleFactor = 1;
        const playCountFactor = 100;
        const sampleFactor =
          baseSampleFactor + Math.floor(playCount / playCountFactor);

        clip.play_count = (clip.play_count || 0) + 1;

        clips.apiClient.POST('/api/gen/{gen_id}/increment_play_count/v2', {
          params: { path: { gen_id: clip.id } },
          body: { sample_factor: sampleFactor },
        });
        setHasBeenPlayed(true);
      }
    }, [clip, edit.clipId, setPreviewEdit, previewClipId]);

    const editTitle = useMemo(() => {
      return clip ? getEditClipTitle(clip) : 'Edit';
    }, [clip]);

    const clipLiked = Boolean(clip && isLiked(clip));
    const clipDisliked = Boolean(clip && isDisliked(clip));

    const handleThumbsUp = useCallback(() => {
      clips.likeClip(edit.clipId, !clipLiked);
    }, [clipLiked, edit, clips]);

    const handleThumbsDown = useCallback(() => {
      clips.dislikeClip(edit.clipId, !clipDisliked);
    }, [clipDisliked, edit, clips]);

    const receiveGhostRef = useCallback((node: HTMLDivElement | null) => {
      if (node) {
        const observer = new IntersectionObserver(
          ([entry]) => {
            if (entry.isIntersecting) {
              setHasDisplayed(true);
              displayedEditIDs[edit.clipId] = true;
              observer.disconnect();
            }
          },
          { threshold: 0.1 }
        );
        observer.observe(node);
      }
    }, []);

    const [imageUrl, setImageUrl] = useState(
      clip?.image_url || 'https://cdn-o.suno.com/auras/Aura-04.jpg'
    );

    useEffect(() => {
      if (clip?.image_url) {
        setImageUrl(clip.image_url);
      }
    }, [clip?.image_url]);

    if (
      !shouldDisplay ||
      clip?.status === 'error' ||
      (clip && isTimedOut(clip))
    ) {
      return null;
    }

    if (clip?.status !== 'complete' || !hasDisplayed)
      return (
        <EditGhost active={false} isLoading={true} ref={receiveGhostRef}>
          <EditStatus>{getUserFacingStatus(clip?.status)}</EditStatus>
          <SpinnerSVG />
        </EditGhost>
      );

    return (
      <EditWrapper
        onClick={handleClick}
        active={previewClipId === edit.clipId}
        isLoading={clip?.status !== 'complete'}
        disliked={clipDisliked}
      >
        <SongImage
          className='h-[67px] w-[48px]'
          onError={() =>
            setImageUrl('https://cdn-o.suno.com/auras/Aura-04.jpg')
          }
          src={imageUrl}
          alt={clip.title}
        />
        {!hasBeenPlayed && clip.play_count === 0 && (
          <div
            className={`absolute top-0 left-0 z-2 h-3 w-3 rounded-full bg-accent-pink`}
          />
        )}
        <EditMeta>
          <EditTitle>
            <a
              href={`/song/${clip.id}`}
              className='hover:underline'
              target='_blank'
            >
              <EditTitleMain>
                {editTitle} #{edit.index + 1}
              </EditTitleMain>
            </a>
            <EditTitleMeta>{timeReadout}</EditTitleMeta>
          </EditTitle>
          <EditTags>{clip.metadata.tags}</EditTags>
        </EditMeta>
        <EditControls>
          <Button
            variant={ButtonVariant.Tertiary}
            size={ButtonSize.Mini}
            icon={FeedbackIcon}
            iconClassName='w-6 h-6'
            onClick={() => {
              openModalWithData(
                ModalTypes.CLIP_FEEDBACK,
                { clipId: clip.id },
                'EditList'
              );
            }}
            aria-label='Feedback Button'
            active={!!clips.feedbackGivenByClipId[clip.id]}
          />
          <Button
            onClick={(e) => {
              handleThumbsUp();
              e.stopPropagation();
            }}
            variant={ButtonVariant.Tertiary}
            size={ButtonSize.Small}
            icon={
              <ThumbsUpIcon
                className={clsx('h-4 w-4', { 'opacity-30': !clipLiked })}
              />
            }
          />
          <Button
            onClick={(e) => {
              handleThumbsDown();
              e.stopPropagation();
            }}
            variant={ButtonVariant.Tertiary}
            size={ButtonSize.Small}
            icon={
              <ThumbsDownIcon
                className={clsx('h-4 w-4', { 'opacity-30': !clipDisliked })}
              />
            }
          />
        </EditControls>
      </EditWrapper>
    );
  }
);

export default function EditList({ clipId }: { clipId: string }) {
  const edits = useEditAncestryQuery(clipId);
  const { generateCount } = useContext(GenerateCountContext);
  const reversedEdits = useMemo(() => {
    if (!edits.data?.childEdits) return [];
    return edits.data.childEdits.slice().reverse();
  }, [edits.data?.childEdits]);

  useEffect(() => {
    edits.refetch();
  }, [generateCount]);

  const [showDisliked, setShowDisliked] = useState(false);
  const [showLiked, setShowLiked] = useState(true);
  const [showUnrated, setShowUnrated] = useState(true);

  return (
    <GlassModule>
      <GlassModuleHeader>
        Edits
        <ContextMenuTrigger
          title='Edit Filters'
          ButtonComponent={(props) => (
            <Button
              variant={ButtonVariant.Tertiary}
              size={ButtonSize.Mini}
              icon={MoreVerticalIcon}
              {...props}
            />
          )}
          ContentsComponent={() => (
            <>
              <ContextMenuItem
                keepMenusOpen
                icon={showDisliked ? <CheckboxIcon /> : <CheckboxOutlineIcon />}
                onClick={() => setShowDisliked(!showDisliked)}
              >
                Show Disliked
              </ContextMenuItem>
              <ContextMenuItem
                keepMenusOpen
                icon={showLiked ? <CheckboxIcon /> : <CheckboxOutlineIcon />}
                onClick={() => setShowLiked(!showLiked)}
              >
                Show Liked
              </ContextMenuItem>
              <ContextMenuItem
                keepMenusOpen
                icon={showUnrated ? <CheckboxIcon /> : <CheckboxOutlineIcon />}
                onClick={() => setShowUnrated(!showUnrated)}
              >
                Show Unrated
              </ContextMenuItem>
            </>
          )}
        ></ContextMenuTrigger>
      </GlassModuleHeader>
      {reversedEdits.length === 0 && (
        <GlassModuleBody className='flex h-full items-center justify-center bg-transparent text-center opacity-50'>
          No edits yet.
          <br />
          Extensions and replacements will appear here!
        </GlassModuleBody>
      )}
      <GlassModuleBody>
        <div className='flex flex-col gap-3 overflow-y-scroll'>
          {reversedEdits.map((edit) => (
            <EditDisplay
              key={edit.clipId}
              edit={edit}
              showLiked={showLiked}
              showDisliked={showDisliked}
              showUnrated={showUnrated}
            />
          ))}
        </div>
      </GlassModuleBody>
    </GlassModule>
  );
}
