import { useEffect, useState } from 'react';
import { AudioPlayer } from './AudioPlayer';
import { Podcast, PodcastTurn } from '../utils/types';
import { parse } from 'yaml';
import {
  addNoise,
  addSilence,
  audioBufferToMp3,
  generateAudio,
  isBlogMode,
  joinAudio,
  loadWavAndDecode,
  pickRand,
  uploadFileToHub,
    denoiseAudioBuffer,
} from '../utils/utils';

// taken from https://freesound.org/people/artxmp1/sounds/660540
import openingSoundSrc from '../opening-sound.wav';
import { getBlogComment } from '../utils/prompts';

interface GenerationStep {
  turn: PodcastTurn;
  audioBuffer?: AudioBuffer;
}

const SPEEDS = [
  { name: 'slow AF', value: 0.8 },
  { name: 'slow', value: 0.9 },
  { name: 'a bit slow', value: 1.0 },
  { name: 'natural', value: 1.1 },
  { name: 'most natural', value: 1.2 },
  { name: 'a bit fast', value: 1.3 },
  { name: 'fast!', value: 1.4 },
  { name: 'fast AF', value: 1.5 },
];

const SPEAKERS = [
  { name: '🇺🇸 🚺 Heart ❤️', value: 'af_heart' },
  { name: '🇺🇸 🚺 Bella 🔥', value: 'af_bella' },
  // { name: '🇺🇸 🚺 Nicole 🎧', value: 'af_nicole' },
  { name: '🇺🇸 🚺 Aoede', value: 'af_aoede' },
  { name: '🇺🇸 🚺 Kore', value: 'af_kore' },
  { name: '🇺🇸 🚺 Sarah', value: 'af_sarah' },
  { name: '🇺🇸 🚺 Nova', value: 'af_nova' },
  { name: '🇺🇸 🚺 Sky', value: 'af_sky' },
  { name: '🇺🇸 🚺 Alloy', value: 'af_alloy' },
  { name: '🇺🇸 🚺 Jessica', value: 'af_jessica' },
  { name: '🇺🇸 🚺 River', value: 'af_river' },
  { name: '🇺🇸 🚹 Michael', value: 'am_michael' },
  { name: '🇺🇸 🚹 Fenrir', value: 'am_fenrir' },
  { name: '🇺🇸 🚹 Puck', value: 'am_puck' },
  { name: '🇺🇸 🚹 Echo', value: 'am_echo' },
  { name: '🇺🇸 🚹 Eric', value: 'am_eric' },
  { name: '🇺🇸 🚹 Liam', value: 'am_liam' },
  { name: '🇺🇸 🚹 Onyx', value: 'am_onyx' },
  { name: '🇺🇸 🚹 Santa', value: 'am_santa' },
  { name: '🇺🇸 🚹 Adam', value: 'am_adam' },
  { name: '🇬🇧 🚺 Emma', value: 'bf_emma' },
  { name: '🇬🇧 🚺 Isabella', value: 'bf_isabella' },
  { name: '🇬🇧 🚺 Alice', value: 'bf_alice' },
  { name: '🇬🇧 🚺 Lily', value: 'bf_lily' },
  { name: '🇬🇧 🚹 George', value: 'bm_george' },
  { name: '🇬🇧 🚹 Fable', value: 'bm_fable' },
  { name: '🇬🇧 🚹 Lewis', value: 'bm_lewis' },
  { name: '🇬🇧 🚹 Daniel', value: 'bm_daniel' },
];

const getRandomSpeakerPair = (): { s1: string; s2: string } => {
  const s1Gender = Math.random() > 0.5 ? '🚺' : '🚹';
  const s2Gender = s1Gender === '🚺' ? '🚹' : '🚺';
  const s1 = pickRand(
    SPEAKERS.filter((s) => s.name.includes(s1Gender) && s.name.includes('🇺🇸'))
  ).value;
  const s2 = pickRand(
    SPEAKERS.filter((s) => s.name.includes(s2Gender) && s.name.includes('🇺🇸'))
  ).value;
  return { s1, s2 };
};

const parseYAML = (yaml: string): Podcast => {
  try {
    return parse(yaml);
  } catch (e) {
    console.error(e);
    throw new Error(
      'invalid YAML, please re-generate the script: ' + (e as any).message
    );
  }
};

const getTimestampedFilename = (ext: string) => {
    return `podcast_${new Date().toISOString().replace(/:/g, "-").split(".")[0]}.${ext}`;
};


export const PodcastGenerator = ({
  genratedScript,
  setBusy,
  blogURL,
  busy,
}: {
  genratedScript: string;
  blogURL: string;
  setBusy: (busy: boolean) => void;
  busy: boolean;
}) => {
  const [wav, setWav] = useState<AudioBuffer | null>(null);
    const [wavBlob, setWavBlob] = useState<Blob | null>(null);
    const [mp3Blob, setMp3Blob] = useState<Blob | null>(null);
  const [numSteps, setNumSteps] = useState<number>(0);
  const [numStepsDone, setNumStepsDone] = useState<number>(0);

  const [script, setScript] = useState<string>('');
  const [speaker1, setSpeaker1] = useState<string>('');
  const [speaker2, setSpeaker2] = useState<string>('');
  const [speed, setSpeed] = useState<string>('1.2');
  const [addIntroMusic, setAddIntroMusic] = useState<boolean>(false);
    const [enableDenoise, setEnableDenoise] = useState<boolean>(false);


  const [blogFilePushToken, setBlogFilePushToken] = useState<string>(
    localStorage.getItem('blogFilePushToken') || ''
  );
  const [blogCmtOutput, setBlogCmtOutput] = useState<string('');
    const [uploadToBlog, setUploadToBlog] = useState<boolean>(false); // Control blog upload


  useEffect(() => {
    localStorage.setItem('blogFilePushToken', blogFilePushToken);
  }, [blogFilePushToken]);

  const setRandSpeaker = () => {
    const { s1, s2 } = getRandomSpeakerPair();
    setSpeaker1(s1);
    setSpeaker2(s2);
  };
  useEffect(setRandSpeaker, []);

  useEffect(() => {
    setScript(genratedScript);
  }, [genratedScript]);

  const generatePodcast = async () => {
    setWav(null);
        setWavBlob(null);
        setMp3Blob(null);
    setBusy(true);
    setBlogCmtOutput('');

      // Blog mode check moved inside the upload section

    let outputWav: AudioBuffer;
    try {
      const podcast = parseYAML(script);
      const { speakerNames, turns } = podcast;
      for (const turn of turns) {
        // normalize it
        turn.nextGapMilisecs =
          Math.max(-600, Math.min(300, turn.nextGapMilisecs)) - 100;
        turn.text = turn.text
          .trim()
          .replace(/’/g, "'")
          .replace(/“/g, '"')
          .replace(/”/g, '"');
      }
      const steps: GenerationStep[] = turns.map((turn) => ({ turn }));
      setNumSteps(steps.length);
      setNumStepsDone(0);
      for (let i = 0; i < steps.length; i++) {
        const step = steps[i];
        const speakerIdx = speakerNames.indexOf(
          step.turn.speakerName as string
        ) as 1 | 0;
        const speakerVoice = speakerIdx === 0 ? speaker1 : speaker2;
        const url = await generateAudio(
          step.turn.text,
          speakerVoice,
          parseFloat(speed)
        );
        step.audioBuffer = await loadWavAndDecode(url);
        if (i === 0) {
          outputWav = step.audioBuffer;
          if (addIntroMusic) {
            const openingSound = await loadWavAndDecode(openingSoundSrc);
            outputWav = joinAudio(openingSound, outputWav!, -2000);
          } else {
            outputWav = addSilence(outputWav!, true, 200);
          }
        } else {
          const lastStep = steps[i - 1];
          outputWav = joinAudio(
            outputWav!,
            step.audioBuffer,
            lastStep.turn.nextGapMilisecs
          );
        }
        setNumStepsDone(i + 1);
      }

        // Denoise if enabled
        if (enableDenoise) {
            outputWav = await denoiseAudioBuffer(outputWav!);
        }

      setWav(outputWav! ?? null);
        // Create WAV blob
        const wavArrayBuffer = outputWav.getChannelData(0).buffer;
        setWavBlob(new Blob([wavArrayBuffer], { type: 'audio/wav' }));

        // Convert to MP3 and create MP3 blob
        const mp3Data = await audioBufferToMp3(outputWav);
        setMp3Blob(new Blob([mp3Data], { type: 'audio/mp3' }));

    } catch (e) {
      console.error(e);
      alert(`Error: ${(e as any).message}`);
        setWav(null);
        setWavBlob(null);
        setMp3Blob(null);
    }
    setBusy(false);
    setNumStepsDone(0);
    setNumSteps(0);

      // Maybe upload to blog
    if (isBlogMode && uploadToBlog && outputWav && blogFilePushToken) {
        if (!blogURL) {
          alert('Please enter a blog slug');
          return; // Stop if no blog URL
        }
      const repoId = 'ngxson/hf-blog-podcast';
      const blogSlug = blogURL.split('/blog/').pop() ?? '_noname';
      const filename = `${blogSlug}.mp3`;  //Use Consistent name for blog
      setBlogCmtOutput(`Uploading '${filename}' ...`);
      try {
        await uploadFileToHub(
            mp3Data, // Use mp3 data from conversion
          filename,
          repoId,
          blogFilePushToken
        );
        setBlogCmtOutput(getBlogComment(filename));
      } catch (uploadError) {
        console.error("Upload failed:", uploadError);
        setBlogCmtOutput(`Upload failed: ${(uploadError as any).message}`);
      }
    }
  };

  const isGenerating = numSteps > 0;

  return (
    <>
      <div className="card bg-base-100 w-full shadow-xl">
        <div className="card-body">
          <h2 className="card-title">Step 2: Script (YAML format)</h2>

          {isBlogMode && (
            <>
              <input
                type="password"
                placeholder="Repo push HF_TOKEN"
                className="input input-bordered w-full"
                value={blogFilePushToken}
                onChange={(e) => setBlogFilePushToken(e.target.value)}
              />
              <div className="flex items-center gap-2">
                <input
                  type="checkbox"
                  className="checkbox"
                  checked={uploadToBlog}
                  onChange={(e) => setUploadToBlog(e.target.checked)}
                  disabled={isGenerating || busy}
                />
                Upload to Blog
              </div>
            </>
          )}

          <textarea
            className="textarea textarea-bordered w-full h-72 p-2"
            placeholder="Type your script here..."
            value={script}
            onChange={(e) => setScript(e.target.value)}
          ></textarea>

          <div className="grid grid-cols-2 gap-4">
            <label className="form-control w-full">
              <div className="label">
                <span className="label-text">Speaker 1</span>
              </div>
              <select
                className="select select-bordered"
                value={speaker1}
                onChange={(e) => setSpeaker1(e.target.value)}
              >
                {SPEAKERS.map((s) => (
                  <option key={s.value} value={s.value}>
                    {s.name}
                  </option>
                ))}
              </select>
            </label>

            <label className="form-control w-full">
              <div className="label">
                <span className="label-text">Speaker 2</span>
              </div>
              <select
                className="select select-bordered"
                value={speaker2}
                onChange={(e) => setSpeaker2(e.target.value)}
              >
                {SPEAKERS.map((s) => (
                  <option key={s.value} value={s.value}>
                    {s.name}
                  </option>
                ))}
              </select>
            </label>

            <button className="btn" onClick={setRandSpeaker}>
              Randomize speakers
            </button>

            <label className="form-control w-full">
              <select
                className="select select-bordered"
                value={speed.toString()}
                onChange={(e) => setSpeed(e.target.value)}
              >
                {SPEEDS.map((s) => (
                  <option key={s.value} value={s.value.toString()}>
                    Speed: {s.name} ({s.value})
                  </option>
                ))}
              </select>
            </label>

            <div className="flex items-center gap-2">
              <input
                type="checkbox"
                className="checkbox"
                checked={addIntroMusic}
                onChange={(e) => setAddIntroMusic(e.target.checked)}
                disabled={isGenerating || busy}
              />
              Add intro music
            </div>

            <div className="flex items-center gap-2">
              <input
                type="checkbox"
                className="checkbox"
                checked={enableDenoise}
                onChange={(e) => setEnableDenoise(e.target.checked)}
                disabled={isGenerating || busy}
              />
              Enable Noise Reduction
            </div>
          </div>

          <button
            id="btn-generate-podcast"
            className="btn btn-primary mt-2"
            onClick={generatePodcast}
            disabled={busy || !script || isGenerating}
          >
            {isGenerating ? (
              <>
                <span className="loading loading-spinner loading-sm"></span>
                Generating ({numStepsDone}/{numSteps})...
              </>
            ) : (
              'Generate podcast'
            )}
          </button>

          {isGenerating && (
            <progress
              className="progress progress-primary mt-2"
              value={numStepsDone}
              max={numSteps}
            ></progress>
          )}
        </div>
      </div>

      {wav && (
        <div className="card bg-base-100 w-full shadow-xl">
          <div className="card-body">
            <h2 className="card-title">Step 3: Listen to your podcast</h2>
            <AudioPlayer audioBuffer={wav} />
            {wavBlob && (
              <a
                className="btn btn-sm btn-primary ml-2"
                href={URL.createObjectURL(wavBlob)}
                download={getTimestampedFilename('wav')}
              >
                Download WAV
              </a>
            )}

            {mp3Blob && (
              <a
                className="btn btn-sm btn-primary ml-2"
                href={URL.createObjectURL(mp3Blob)}
                download={getTimestampedFilename('mp3')}
              >
                Download MP3
              </a>
            )}

            {isBlogMode && (
              <div>
                -------------------
                <br />
                <h2>Comment to be posted:</h2>
                <pre className="p-2 bg-base-200 rounded-md my-2 whitespace-pre-wrap break-words">
                  {blogCmtOutput}
                </pre>
                <button
                  className="btn btn-sm btn-secondary"
                  onClick={() => copyStr(blogCmtOutput)}
                >
                  Copy comment
                </button>
              </div>
            )}
          </div>
        </div>
      )}
    </>
  );
};

// copy text to clipboard
export const copyStr = (textToCopy: string) => {
  // Navigator clipboard api needs a secure context (https)
  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard.writeText(textToCopy);
  } else {
    // Use the 'out of viewport hidden text area' trick
    const textArea = document.createElement('textarea');
    textArea.value = textToCopy;
    // Move textarea out of the viewport so it's not visible
    textArea.style.position = 'absolute';
    textArea.style.left = '-999999px';
    document.body.prepend(textArea);
    textArea.select();
    document.execCommand('copy');
  }
};