import clsx from 'clsx';
import { format } from 'date-fns';
import { useAnimationFrame } from 'framer-motion';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import Button, { ButtonShape, ButtonVariant } from '@/components/button/Button';
import Link from '@/components/link/Link';
import { ModalTypes } from '@/components/modal/constants/ModalTypes';
import { toast } from '@/components/toast/Toast';
import useRecording, { RecordingStage } from '@/hooks/useRecording';
import { UploadIcon } from '@/icons';
import audioContext from '@/lib/audioContext';
import getNormalizedAudioBuffer from '@/lib/getNormalizedAudioBuffer';
import traceWaveform from '@/lib/traceWaveform';
import { PlanFeature } from '@/state/sessionStore';
import { AUDIO_FILE_TYPES_STRING } from '@/utils/constants';
import removeFileExtension from '@/utils/removeFileExtension';
import { isFeatureEnabledForPlan } from '@/utils/session';
import { encodeTimeFormat } from '@/utils/utils';

import UploadStateContext, { UploadFileConfig } from './UploadStateContext';
import { Footer, MainContent, Wrapper } from './components';

const UploadOrRecord = () => {
  const {
    //setIsRecordingMode,
    //handleGoBack,
    //minSeconds,
    //maxSeconds,
    setUploadFileConfig,
    attemptClose,
  } = useContext(UploadStateContext);

  const fileInputRef = useRef<HTMLInputElement>(null);
  const [duration, setDuration] = useState(0);
  const { menus, session } = useStores();
  const uploadedFileRef = useRef<boolean>(false);

  const handleFileSelect = useCallback(
    async (e: { target: { files: File[] | FileList | null } }) => {
      const files = Array.from(e.target.files || []);
      if (files.length === 0) {
        setUploadFileConfig((current) => ({
          ...(current as UploadFileConfig),
          clientSelectedFile: null,
        }));
        return;
      }

      const file = files[0];
      const arrayBuffer = await file.arrayBuffer();
      const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
      setUploadFileConfig({
        clientSelectedFile: file,
        audioBuffer,
        title: removeFileExtension(file.name),
      });
      uploadedFileRef.current = true;

      if (fileInputRef.current) fileInputRef.current.value = '';
    },
    [setUploadFileConfig]
  );

  const triggerErrorToast = useCallback((title: string) => {
    toast({
      title,
      status: 'error',
      duration: 4000,
      isClosable: true,
    });
  }, []);

  const [isDragging, setIsDragging] = useState(false);

  const handleDragOver = useCallback((e: DragEvent) => {
    e.preventDefault();
    setIsDragging(true);
  }, []);

  const handleDragLeave = useCallback(() => setIsDragging(false), []);

  const handleDrop = useCallback(
    async (e: DragEvent) => {
      e.preventDefault();
      setIsDragging(false);
      if (!e.dataTransfer) return;

      const files = Array.from(e.dataTransfer.files);

      if (files.length > 1) {
        return triggerErrorToast('Please upload one file at a time.');
      }

      const file = files[0];

      if (!file.type.startsWith('audio/')) {
        return triggerErrorToast('Please upload audio files only.');
      }

      try {
        handleFileSelect({ target: { files } } as any);
      } catch (error) {
        return triggerErrorToast(error as string);
      }
    },
    [handleFileSelect, triggerErrorToast]
  );

  useEffect(() => {
    window.addEventListener('dragover', handleDragOver);
    window.addEventListener('dragleave', handleDragLeave);
    window.addEventListener('drop', handleDrop);
    return () => {
      window.removeEventListener('dragover', handleDragOver);
      window.removeEventListener('dragleave', handleDragLeave);
      window.removeEventListener('drop', handleDrop);
    };
  }, [handleDragOver, handleDragLeave, handleDrop]);

  const {
    startRecording,
    stopRecording,
    stage,
    visualizedWindow,
    getCurrentDuration,
  } = useRecording();

  //const hasStartedRecordingRef = useRef(false);
  // useEffect(() => {
  //   if (!hasStartedRecordingRef.current) {
  //     hasStartedRecordingRef.current = true;
  //     startRecording();
  //   }
  // }, [startRecording]);

  const canvasRef = useRef<HTMLCanvasElement>(null);

  useAnimationFrame(
    useCallback(() => {
      const canvas = canvasRef.current;
      if (!canvas || stage !== RecordingStage.Recording) 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;

      const duration = getCurrentDuration();
      setDuration(duration);

      const samplesToRender = Math.min(
        visualizedWindow.length,
        // important subtlety:
        // the worklet is going to smoosh new samples into visualizedWindow, 128 at a time.
        // here, we show a sliced subset of visualizedWindow with (width * 128) samples in it.
        // this means each pixel of width covers 128 samples of audio. when the worklet runs, its buckets of 128 samples will amount to an __integer number__ of pixels' worth of data being inserted to the window.
        // this results in a smoother scrolling effect. if we don't do this, then pixel X=N-1 will not be gauaranteed to have the same samples in it that pixel X=N had last frame, resulting in visible shimmery aliasing.
        Math.ceil(canvas.width) * 128
      );

      //ctx.fillStyle = '#666';
      ctx.fillStyle = 'rgba(0, 0, 0, 0)';

      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // the shape we're filling in is everything *but* the waveform, so this color is actually the background color.
      // the color of the waveform itself is set via the background on the canvas element below.
      //ctx.fillStyle = 'rgba(0, 0, 0, 0)';
      //ctx.globalCompositeOperation = 'destination-out';
      ctx.beginPath();

      traceWaveform({
        ctx,
        channelData: visualizedWindow.subarray(
          visualizedWindow.length - samplesToRender
        ),
        topPadding: 0,
        inverse: true, // using `inverse` here keeps identical styling between Recording and Trim, even though only Trim really needs it.
        normalize: false, // normalizing looks very weird here - waveform changes height suddenly when input gets louder.
        enableCache: false, // we can't cache any of the down-sampled waveform data because the content of `channelData` will be different every frame.
        useLineVariant: true,
        pxPerPoint: 2,
      });
      //ctx.fill();
      ctx.stroke();

      // ctx.font = `${12 * window.devicePixelRatio}px sans-serif`;
      // ctx.fillStyle = 'white';
      // ctx.fillText(
      //   duration > 0 ? encodeTimeFormat(duration / 1000) || '' : '',
      //   24,
      //   42
      // );
    }, [visualizedWindow, stage])
  );

  const handleStopRecording = useCallback(async () => {
    const buffer = await stopRecording();
    if (!buffer) return;
    setUploadFileConfig({
      audioBuffer: getNormalizedAudioBuffer(buffer),
      title: `${buffer.duration.toFixed(1)}s Recording (${format(new Date(), 'MMM d @ h:mm a')})`,
      clientSelectedFile: null,
    });
  }, [stopRecording, setUploadFileConfig]);

  // useEffect(() => {
  //   const handleWindowFocus = () => {
  //     if (triggeredFileUpload.current) {
  //       setTimeout(() => {
  //         if (triggeredFileUpload.current && !uploadedFileRef.current) {
  //           attemptClose();
  //         }
  //       }, 1000);
  //     }
  //   };
  //   if (triggeredFileUpload.current) {
  //     window.addEventListener('focus', handleWindowFocus);
  //   }
  //   return () => {
  //     window.removeEventListener('focus', handleWindowFocus);
  //   };
  // }, [triggeredFileUpload.current, fileInputRef.current]);

  // useEffect(() => {
  //   console.log(triggeredFileUpload.current);
  //   if (
  //     createV2.audioUploadMode === 'uploading' &&
  //     fileInputRef.current &&
  //     !triggeredFileUpload.current
  //   ) {
  //     triggeredFileUpload.current = true;
  //     fileInputRef.current?.click();
  //   }
  // }, [
  //   createV2.audioUploadMode,
  //   fileInputRef.current,
  //   triggeredFileUpload.current,
  // ]);

  return (
    <Wrapper>
      {/* <TitleLine>Audio</TitleLine> */}
      <MainContent
        className={clsx('flex flex-col justify-center', {
          //hidden: createV2.audioUploadMode === 'uploading',
        })}
      >
        <div className='flex h-full flex-1 flex-col gap-4'>
          <div className='flex flex-row justify-center'>
            <span className='font-mono font-light text-foreground-primary'>
              {encodeTimeFormat(duration / 1000)}
            </span>
          </div>
          <div
            className={clsx(
              'align-center flex hidden flex-1 flex-col justify-center gap-4 rounded-[20px] border border-border-primary text-center',
              { ['border-white']: isDragging }
            )}
          >
            <input
              type='file'
              ref={fileInputRef}
              onChange={handleFileSelect}
              accept={AUDIO_FILE_TYPES_STRING}
              className='hidden'
            />
            <div className='flex flex-row justify-center gap-2'>
              <Button
                className='w-auto bg-background-secondary'
                variant={ButtonVariant.Standard}
                shape={ButtonShape.Pill}
                icon={<UploadIcon />}
                onClick={() => fileInputRef.current?.click()}
              >
                Upload
              </Button>
              <Button
                className='w-auto bg-background-secondary'
                variant={ButtonVariant.Standard}
                shape={ButtonShape.Pill}
                icon={<UploadIcon />}
                onClick={() => {
                  attemptClose();
                  menus.openModal(ModalTypes.LIBRARY_SELECT);
                }}
              >
                Library
              </Button>
            </div>
            <div className='flex flex-row justify-center'>
              <span className='text-sm text-foreground-tertiary'>
                Or drag and drop audio here
              </span>
            </div>
          </div>
          <div className='flex flex-1 flex-col rounded-[20px] bg-transparent'>
            <div
              className={'relative flex w-full flex-1 flex-row items-center'}
            >
              <canvas
                ref={canvasRef}
                className='absolute top-[calc(50%-100px)] right-0 bottom-0 left-0 h-[200px] w-full bg-transparent'
              />
              {/*Array.from({ length: 13 }).map((_, i) => (
                <div
                  key={i}
                  className={clsx('flex-1 hidden', {
                    'border-r-[0.5px] border-border-secondary': i < 12,
                  })}
                ></div>
              ))*/}
            </div>
            <div className='flex h-auto w-full flex-row items-center border-t-[0.5px] border-border-secondary p-2'>
              <div className='flex w-full flex-row items-center justify-center gap-2'>
                <Button
                  shape={ButtonShape.Pill}
                  className={clsx(
                    'h-[100px] w-[100px] border-4 border-border-primary-glass p-0',
                    {
                      'animate-pulse before:bg-background-primary-glass enabled:hover:before:bg-background-primary-glass':
                        stage === RecordingStage.Recording,
                      'before:bg-vermilion-500 enabled:hover:before:bg-vermilion-400':
                        stage !== RecordingStage.Recording,
                    }
                  )}
                  onClick={() => {
                    if (stage === RecordingStage.Recording) {
                      handleStopRecording();
                    } else {
                      startRecording();
                    }
                  }}
                >
                  {stage === RecordingStage.Recording ? (
                    <div className='h-[60px] w-[60px] rounded-lg bg-vermilion-400'></div>
                  ) : null}
                </Button>
                {/* <span className='text-foreground-secondary'>
                  {stage === RecordingStage.Recording ? 'Recording' : 'Record'}
                </span> */}
              </div>
            </div>
          </div>
        </div>
      </MainContent>

      <Footer>
        {isFeatureEnabledForPlan(session, PlanFeature.LongUploads) ? (
          <p className='w-full text-center font-mono text-[10px] text-foreground-secondary uppercase'>
            8 min limit for audio uploads.
          </p>
        ) : (
          <p className='w-full text-center font-mono text-[10px] text-foreground-secondary uppercase'>
            1 min limit.{' '}
            <Link href={'/account'} className='underline'>
              Upgrade
            </Link>{' '}
            to use longer audio (8 min)
          </p>
        )}
      </Footer>
    </Wrapper>
  );
};

export default UploadOrRecord;
