'use client';

/* eslint jsx-a11y/click-events-have-key-events: warn */

/* eslint jsx-a11y/no-noninteractive-element-interactions: warn */
import clsx from 'clsx';
import { useAnimationFrame } from 'framer-motion';
import {
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import Button, {
  ButtonShape,
  ButtonSize,
  ButtonVariant,
} from '@/components/button/Button';
import Link from '@/components/link/Link';
import useClickDrag from '@/hooks/useClickDrag';
import { EditIcon, PauseIcon, PlayIcon } from '@/icons';
import makeWavBuffer from '@/lib/makeWavBuffer';
import { PlanFeature } from '@/state/sessionStore';
import { isFeatureEnabledForPlan } from '@/utils/session';
//import { PRO_MAX_UPLOAD_SECONDS } from '@/utils/constants';
import { encodeTimeFormat } from '@/utils/utils';

import useAudioBufferPlayback from '../../../../hooks/useAudioBufferPlayback';
import traceWaveform from '../../../../lib/traceWaveform';
import trimAudioBuffer from '../../../../lib/trimAudioBuffer';
import { AudioActions } from '../v2/types';
import { CanvasWrapper } from './CanvasWrapper';
import DraggableTrimRegion from './DraggableTrimRegion';
import UploadStateContext, { UploadFileConfig } from './UploadStateContext';
import { Footer, MainContent, Wrapper } from './components';

const Trim = ({
  autoSaveOnFinalize = false,
}: {
  autoSaveOnFinalize?: boolean;
}) => {
  const {
    uploadFileConfig,
    setUploadFileConfig,
    setPendingClipContext,
    trimRange,
    setTrimRange,
    setTrimmedFile,
    handleGoBack,
    minSeconds,
    maxSeconds,
    setInitializationCondition,
    initializationCondition,
    onClose,
    isVoxPersonaUpload,
  } = useContext(UploadStateContext);

  const { audioBuffer, clientSelectedFile, title } = uploadFileConfig ?? {};
  const { createV2, session } = useStores();
  const { playing, play, stop, seek, load, getCurrentTime, setEndTime } =
    useAudioBufferPlayback();

  const togglePlayback = useCallback(() => {
    if (playing) {
      stop();
      //seek(trimRange[0]);
    } else {
      //play(undefined, trimRange[1]);
      play(undefined);
    }
  }, [play, stop, playing, trimRange]);

  useEffect(() => {
    if (audioBuffer) load(audioBuffer);
  }, [audioBuffer, load]);

  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [currentTime, setCurrentTime] = useState('');
  const trimRangePreviewRef = useRef(trimRange);
  const firstChannelData = useMemo(
    () => audioBuffer?.getChannelData(0),
    [audioBuffer]
  );

  // Vox persona upload limits -- update these as needed, probably want to find somewhere better to maintain this
  const voxMinSeconds = 10;
  const voxMaxSeconds = 30;

  useAnimationFrame(
    useCallback(() => {
      if (!audioBuffer || !firstChannelData) return;

      const canvas = canvasRef.current;
      if (!canvas) return;

      const ctx = canvas.getContext('2d');
      if (!ctx) return;

      const rect = canvas.getBoundingClientRect();
      canvas.width = rect.width * window.devicePixelRatio;
      canvas.height = rect.height * window.devicePixelRatio;
      //ctx.fillStyle = '#666';
      ctx.fillStyle = 'rgba(0, 0, 0, 0)';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      const [start, end] = trimRangePreviewRef.current;
      const width = canvas.width;
      const height = canvas.height;
      const startPx = (start / audioBuffer.duration) * width;
      const endPx = (end / audioBuffer.duration) * width;

      // just like in Recording.tsx, the shape we're ultimately going to fill is the space above and below the actual waveform.
      // the waveform's color is set via the background on the canvas element below.

      // one difference from recording: we also fill in the regions before and after the trim range with the same color as above and below the waveform.
      // ctx.fillRect(0, 0, startPx, height);
      // ctx.fillRect(endPx, 0, width, height);

      ctx.lineWidth = 2 * window.devicePixelRatio;

      // the portions of the waveform that are outside the trim range have an outline.
      // we clip to just the pre- and post-trim regions so that this outline doesn't interfere with the display of the waveform inside the trim range.
      ctx.save();
      ctx.beginPath();
      ctx.rect(0, 0, startPx, height);
      ctx.rect(endPx, 0, width, height);
      ctx.clip();

      ctx.beginPath();
      traceWaveform({
        ctx,
        channelData: firstChannelData,
        topPadding: 0,
        useLineVariant: true,
        // topPadding: 20 * window.devicePixelRatio,
      });
      ctx.stroke();
      ctx.restore();

      // ctx.globalCompositeOperation = 'destination-out';
      // now, we do the main waveform trace, to fill the space above and below it.
      ctx.beginPath();
      traceWaveform({
        ctx,
        channelData: firstChannelData,
        topPadding: 0, //20 * window.devicePixelRatio,
        inverse: true,
        useLineVariant: true,
      });
      ctx.stroke();

      // this is a subtle dark overlay on the pre- and post-trim regions to make the trimmed portion pop a bit more.
      // ctx.fillStyle = '#00000099';
      // ctx.fillRect(0, 0, startPx, height);
      // ctx.fillRect(endPx, 0, width, height);
    }, [audioBuffer, firstChannelData, trimRangePreviewRef])
  );

  useEffect(() => {
    trimRangePreviewRef.current = trimRange;
  }, [trimRange]);

  const playheadRef = useRef<HTMLDivElement>(null);

  const isTrimValid = useMemo(() => {
    if (!isVoxPersonaUpload) {
      return true;
    } // Only validate for vox persona
    const trimDuration = trimRange[1] - trimRange[0];
    return trimDuration >= voxMinSeconds && trimDuration <= voxMaxSeconds;
  }, [isVoxPersonaUpload, trimRange]);

  useAnimationFrame(
    useCallback(() => {
      if (!audioBuffer) return;

      const playhead = playheadRef.current;
      if (!playhead) return;

      playhead.style.left = `${(getCurrentTime() / audioBuffer.duration) * 100}%`;
      const encodedCurrentTime = encodeTimeFormat(getCurrentTime());
      if (encodedCurrentTime !== currentTime) {
        setCurrentTime(encodedCurrentTime || '0:00');
      }
    }, [audioBuffer, getCurrentTime, currentTime])
  );

  const contentUnchangedByTrim = useMemo(
    () => trimRange[0] === 0 && trimRange[1] === audioBuffer?.duration,
    [trimRange, audioBuffer]
  );

  const handleSave = useCallback(async () => {
    if (!audioBuffer) return;

    // For studio uploads, set up automatic save and close the modal
    if (autoSaveOnFinalize) {
      // Upload tracking will be handled by UploadStateContext when upload starts

      // Set to auto-save mode
      setInitializationCondition(AudioActions.SAVE);

      // Store title in pending clip context for use when upload completes
      setPendingClipContext({ title: title });
    }

    if (clientSelectedFile && contentUnchangedByTrim) {
      // if no trim was applied, upload the original file, unchanged.
      setTrimmedFile(clientSelectedFile);
    } else {
      const paddedMinSeconds = minSeconds + 0.5; // for some reason the backend thinks the output is a bit shorter? weird

      const wavBuffer = await makeWavBuffer(
        trimAudioBuffer({
          audioBuffer,
          minLengthSeconds: paddedMinSeconds,
          maxLengthSeconds: maxSeconds,
          trimStart: trimRange[0],
          trimEnd: trimRange[1],
          maintainEnd: true,
        })
      );

      let filename = `Uploaded Audio.wav`;
      if (clientSelectedFile) {
        filename = clientSelectedFile.name;
      } else if (title) {
        filename = `${title}.wav`;
      }

      const file = new File([wavBuffer], filename, { type: 'audio/wav' });

      setTrimmedFile(file);
    }

    // For studio uploads, close modal immediately and let upload continue in background
    if (autoSaveOnFinalize) {
      onClose?.(initializationCondition);
    }
  }, [
    audioBuffer,
    clientSelectedFile,
    contentUnchangedByTrim,
    minSeconds,
    maxSeconds,
    trimRange,
    setTrimmedFile,
    autoSaveOnFinalize,
    createV2,
    setInitializationCondition,
    title,
  ]);

  //const isPro = useStores().session?.roles?.['pro'] || false;

  const mustBeTrimmed = (audioBuffer?.duration || 0) > maxSeconds;

  const getSecondsFromPx = useCallback(
    (px: number) => {
      const canvas = canvasRef.current;
      if (!canvas || !audioBuffer) return 0;
      const width = canvas.getBoundingClientRect().width;
      return (px / width) * audioBuffer.duration;
    },
    [audioBuffer?.duration]
  );

  const seekToPx = useCallback(
    (px: number) => {
      if (!canvasRef.current) return;
      const x = px - canvasRef.current.getBoundingClientRect().left;
      seek(Math.max(trimRange[0], Math.min(trimRange[1], getSecondsFromPx(x))));
    },
    [seek, trimRange, getSecondsFromPx]
  );

  const receivePlayheadDragTarget = useClickDrag(
    useCallback(
      ({ clientX }) => {
        const wasPlaying = playing;
        if (wasPlaying) stop();
        seekToPx(clientX);
        return {
          onMouseMove: ({ clientX }) => {
            seekToPx(clientX);
          },
          onMouseUp: () => {
            if (wasPlaying) play();
          },
        };
      },
      [seekToPx, playing, play, stop]
    )
  );

  const [isEditingTitle, setIsEditingTitle] = useState(false);
  const titleInputRef = useRef<HTMLInputElement>(null);

  return (
    <Wrapper>
      {/* <TitleLine className='text-center w-full hidden'>Trim Audio</TitleLine> */}
      <div className='flex flex-col items-center'>
        <div className='mb-2 flex flex-row items-center'>
          {!isEditingTitle ? (
            <h3
              className='line-clamp-1 max-w-[200px] cursor-pointer text-xl font-medium break-all text-foreground-secondary'
              onClick={() => {
                setIsEditingTitle(true);
                requestAnimationFrame(() => {
                  titleInputRef.current?.select();
                });
              }}
            >
              {title ?? 'Untitled Song'}
            </h3>
          ) : (
            <input
              type='text'
              ref={titleInputRef}
              className='border-0 bg-transparent text-xl font-medium text-foreground-secondary outline-none'
              value={title ?? 'Untitled Song'}
              onChange={(e) =>
                setUploadFileConfig({
                  ...((uploadFileConfig ?? {}) as UploadFileConfig),
                  title: e.target.value,
                })
              }
              onKeyDown={(e) => {
                if (e.key === 'Enter') {
                  setIsEditingTitle(false);
                }
              }}
              onBlur={() => setIsEditingTitle(false)}
            />
          )}
          <Button
            iconStart={EditIcon}
            className='border-0 bg-transparent p-0'
            onClick={() => {
              setIsEditingTitle(true);
              requestAnimationFrame(() => {
                titleInputRef.current?.select();
              });
            }}
          />
        </div>
        <span className='font-mono font-light text-foreground-primary'>
          {encodeTimeFormat(trimRange[1] - trimRange[0])} /{' '}
          {encodeTimeFormat(audioBuffer?.duration)}
        </span>
      </div>

      <MainContent className='relative flex h-[400px] flex-col justify-center'>
        <div className='flex h-full max-h-[100px] flex-col gap-4'>
          <div className='flex h-full flex-row gap-2'>
            <div className='flex h-full max-h-[100px] flex-col justify-center'>
              <Button
                variant={ButtonVariant.Secondary}
                icon={
                  playing ? (
                    <PauseIcon className='h-6 w-6' />
                  ) : (
                    <PlayIcon className='h-6 w-6' />
                  )
                }
                onClick={() => togglePlayback()}
                shape={ButtonShape.Rounded}
                size={ButtonSize.Large}
                className='h-[100px] w-[100px]'
              />
            </div>
            <CanvasWrapper
              canvasRef={canvasRef}
              renderCanvasOverlay={() => {
                return (
                  <>
                    <div
                      className={clsx(
                        'pointer-events-none absolute top-0 bottom-0 w-[2px] bg-foreground-primary',
                        {
                          'opacity-0': !audioBuffer,
                        }
                      )}
                      ref={playheadRef}
                    />
                    <div
                      className='absolute inset-0 top-[20px]'
                      ref={receivePlayheadDragTarget}
                    />
                    {audioBuffer?.duration && (
                      <DraggableTrimRegion
                        audioBufferDuration={audioBuffer.duration}
                        trimRange={trimRange}
                        setTrimRange={setTrimRange}
                        trimRangePreviewRef={trimRangePreviewRef}
                        onSetEndTime={setEndTime}
                        maxSeconds={
                          isVoxPersonaUpload ? voxMaxSeconds : maxSeconds
                        }
                      />
                    )}
                  </>
                );
              }}
            />
          </div>
        </div>
      </MainContent>

      <Footer
        className={clsx('relative flex flex-col justify-center gap-2', {
          '-mt-1': mustBeTrimmed,
        })}
      >
        <div className='flex w-full flex-row justify-center gap-2 px-4'>
          <Button
            variant={ButtonVariant.Standard}
            className='flex-1 bg-background-fog-thin'
            onClick={handleGoBack}
            size={ButtonSize.Large}
            shape={ButtonShape.Pill}
          >
            Start Over
          </Button>
          <Button
            variant={ButtonVariant.Primary}
            onClick={handleSave}
            size={ButtonSize.Large}
            shape={ButtonShape.Pill}
            className='flex-1'
            disabled={!isTrimValid}
          >
            Save
          </Button>
        </div>
        {/*mustBeTrimmed && (
          <div className='absolute -bottom-[26px] text-sm'>
            Trim up to {maxSeconds} seconds of audio.
            {!isPro &&
              maxSeconds < PRO_MAX_UPLOAD_SECONDS &&
              ` (Upgrade to Pro for ${PRO_MAX_UPLOAD_SECONDS}s uploads.)`}
          </div>
        )*/}
        <p className='w-full text-center font-mono text-[10px] text-foreground-secondary uppercase'>
          Trim your audio to between{' '}
          {isVoxPersonaUpload ? voxMinSeconds : minSeconds} seconds and{' '}
          {isVoxPersonaUpload ? voxMaxSeconds : Math.floor(maxSeconds / 60)}{' '}
          {isVoxPersonaUpload ? 'seconds' : 'min'} long.{' '}
          {!isFeatureEnabledForPlan(session, PlanFeature.LongUploads) ? (
            <span>
              <Link href={'/account'} className='underline'>
                Upgrade
              </Link>{' '}
              to use longer audio (8 min)
            </span>
          ) : null}
        </p>
      </Footer>
    </Wrapper>
  );
};

export default Trim;
