"use client";

import { useMemo, useState } from "react";
import { Download, Waves } from "lucide-react";

import {
  DualRangeSlider,
  ExportPanel,
  MediaUploadDropzone,
  MetadataPanel,
  ProcessingStatus,
  VideoPlayerPreview
} from "@/components/media";
import { EmptyState } from "@/components/shared/empty-state";
import { ErrorState } from "@/components/shared/error-state";
import { SimpleToastStack } from "@/components/shared/simple-toast-stack";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue
} from "@/components/ui/select";
import { useObjectUrl } from "@/hooks/use-object-url";
import { useSimpleToast } from "@/hooks/use-simple-toast";
import { useDualSliderSync } from "@/hooks/use-slider-sync";
import { parseApiErrorMessage, parseNumberHeader } from "@/lib/client/api-utils";
import { formatBytes } from "@/lib/client/file-validation";
import { clampNumber, formatSecondsAsClock } from "@/lib/client/time-format";
import type { ApiSuccessResponse } from "@/types/api";
import type { MetadataItem } from "@/types/media-ui";
import type { AudioOutputFormat, VideoMetadata } from "@/types/media-processing";

type ExtractionMode = "full" | "range";

function WaveformPlaceholder({
  durationSeconds,
  mode,
  startSeconds,
  endSeconds
}: {
  durationSeconds: number;
  mode: ExtractionMode;
  startSeconds: number;
  endSeconds: number;
}) {
  const bars = useMemo(
    () =>
      Array.from({ length: 52 }, (_, index) => {
        const normalized = index / 51;
        const wave =
          Math.abs(Math.sin(normalized * Math.PI * 3.4)) * 0.65 +
          Math.abs(Math.cos(normalized * Math.PI * 8.2)) * 0.35;

        return Math.round(20 + wave * 70);
      }),
    []
  );

  return (
    <section className="rounded-xl border border-white/10 bg-slate-900/55 p-4">
      <div className="mb-3 flex items-center justify-between gap-2">
        <p className="text-xs uppercase tracking-wide text-slate-400">Waveform Preview</p>
        <p className="text-xs text-slate-400">Placeholder</p>
      </div>
      <div className="flex h-20 items-end gap-[3px]">
        {bars.map((barHeight, index) => {
          const timestamp = (index / (bars.length - 1)) * durationSeconds;
          const isInSelection =
            mode === "range" ? timestamp >= startSeconds && timestamp <= endSeconds : true;

          return (
            <span
              key={`wave-bar-${index}`}
              className={`w-full rounded-full transition-colors ${
                isInSelection ? "bg-sky-300/70" : "bg-slate-600/45"
              }`}
              style={{
                height: `${barHeight}%`
              }}
            />
          );
        })}
      </div>
    </section>
  );
}

export function ExtractAudioTool() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const sourceObjectUrl = useObjectUrl(selectedFile);

  const [metadata, setMetadata] = useState<VideoMetadata | null>(null);
  const [metadataState, setMetadataState] = useState<"idle" | "processing" | "success" | "error">(
    "idle"
  );
  const [metadataError, setMetadataError] = useState<string | null>(null);

  const [extractionMode, setExtractionMode] = useState<ExtractionMode>("full");
  const [outputFormat, setOutputFormat] = useState<AudioOutputFormat>("mp3");

  const maxDurationSeconds = Math.max(metadata?.durationSeconds || 0, 0.01);
  const minimumGapSeconds = 0.01;

  const {
    start,
    end,
    startInput,
    endInput,
    setStartInput,
    setEndInput,
    setRange,
    commitStartInput,
    commitEndInput
  } = useDualSliderSync({
    min: 0,
    max: maxDurationSeconds,
    initialStart: 0,
    initialEnd: maxDurationSeconds,
    minimumGap: minimumGapSeconds
  });

  const [extractionState, setExtractionState] = useState<"idle" | "processing" | "success" | "error">(
    "idle"
  );
  const [extractionProgress, setExtractionProgress] = useState(0);
  const [extractionError, setExtractionError] = useState<string | null>(null);
  const [extractedBlob, setExtractedBlob] = useState<Blob | null>(null);
  const [extractedFileName, setExtractedFileName] = useState<string>("audio.mp3");
  const [extractedFormat, setExtractedFormat] = useState<AudioOutputFormat | null>(null);
  const [exportedDurationSeconds, setExportedDurationSeconds] = useState<number | null>(null);
  const extractedObjectUrl = useObjectUrl(extractedBlob);

  const { toasts, pushToast, dismissToast } = useSimpleToast();

  const metadataItems: MetadataItem[] = useMemo(() => {
    if (!metadata) {
      return [];
    }

    return [
      {
        key: "filename",
        label: "Filename",
        value: metadata.filename
      },
      {
        key: "duration",
        label: "Duration",
        value: formatSecondsAsClock(metadata.durationSeconds)
      },
      {
        key: "resolution",
        label: "Resolution",
        value: `${metadata.width} x ${metadata.height}`
      },
      {
        key: "framerate",
        label: "Frame Rate",
        value: metadata.frameRate ? `${metadata.frameRate.toFixed(2)} fps` : "Unknown"
      },
      {
        key: "audio",
        label: "Audio Stream",
        value: metadata.hasAudio ? "Present" : "Not Detected"
      },
      {
        key: "audioCodec",
        label: "Audio Codec",
        value: metadata.audioCodec ?? "Unknown"
      }
    ];
  }, [metadata]);

  const rangeValidationMessage = useMemo(() => {
    if (extractionMode !== "range" || !metadata) {
      return null;
    }

    if (end <= start) {
      return "End time must be after start time.";
    }

    if (start < 0 || end > metadata.durationSeconds) {
      return "Selected range must stay within source duration.";
    }

    return null;
  }, [end, extractionMode, metadata, start]);

  const canExtract =
    Boolean(selectedFile) &&
    Boolean(metadata) &&
    metadataState === "success" &&
    metadata?.hasAudio === true &&
    !rangeValidationMessage;

  const handleClearMedia = () => {
    setSelectedFile(null);
    setMetadata(null);
    setMetadataState("idle");
    setMetadataError(null);
    setExtractionMode("full");
    setOutputFormat("mp3");
    setRange(0, 0.01);
    setExtractionState("idle");
    setExtractionProgress(0);
    setExtractionError(null);
    setExtractedBlob(null);
    setExtractedFileName("audio.mp3");
    setExtractedFormat(null);
    setExportedDurationSeconds(null);
  };

  const loadMetadata = async (file: File) => {
    setMetadataState("processing");
    setMetadataError(null);
    setMetadata(null);

    try {
      const formData = new FormData();
      formData.set("file", file);

      const response = await fetch("/api/tools/extract-audio/metadata", {
        method: "POST",
        body: formData
      });

      if (!response.ok) {
        const message = await parseApiErrorMessage(response, "Failed to load video metadata.");
        throw new Error(message);
      }

      const payload = (await response.json()) as ApiSuccessResponse<VideoMetadata>;
      if (!payload.ok) {
        throw new Error("Unexpected metadata response.");
      }

      const duration = Math.max(payload.data.durationSeconds, 0.01);
      setMetadata(payload.data);
      setMetadataState("success");
      setRange(0, duration);

      if (!payload.data.hasAudio) {
        pushToast({
          title: "No audio stream found",
          description: "This source does not contain an audio stream to extract.",
          variant: "error"
        });
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to load metadata.";
      setMetadataState("error");
      setMetadataError(message);
      pushToast({
        title: "Metadata failed",
        description: message,
        variant: "error"
      });
    }
  };

  const handleExtractAudio = async () => {
    if (!selectedFile || !metadata) {
      return;
    }

    if (extractionMode === "range" && rangeValidationMessage) {
      setExtractionState("error");
      setExtractionError(rangeValidationMessage);
      pushToast({
        title: "Invalid range",
        description: rangeValidationMessage,
        variant: "error"
      });
      return;
    }

    setExtractionState("processing");
    setExtractionProgress(12);
    setExtractionError(null);
    setExtractedBlob(null);
    setExtractedFormat(null);
    setExportedDurationSeconds(null);

    const progressTimer = setInterval(() => {
      setExtractionProgress((previousProgress) => Math.min(previousProgress + Math.random() * 13, 91));
    }, 220);

    try {
      const formData = new FormData();
      formData.set("file", selectedFile);
      formData.set("mode", extractionMode);
      formData.set("format", outputFormat);

      if (extractionMode === "range") {
        formData.set("startSeconds", start.toFixed(3));
        formData.set("endSeconds", end.toFixed(3));
      }

      const response = await fetch("/api/tools/extract-audio/extract", {
        method: "POST",
        body: formData
      });

      if (!response.ok) {
        const message = await parseApiErrorMessage(response, "Audio extraction failed.");
        throw new Error(message);
      }

      const audioBlob = await response.blob();
      if (!audioBlob || audioBlob.size <= 0) {
        throw new Error("Extracted audio was empty.");
      }

      const nextFileName =
        response.headers.get("x-videohaus-file-name") ||
        `${selectedFile.name.replace(/\.[^.]+$/, "")}-audio.${outputFormat}`;
      const formatFromHeader = response.headers.get("x-videohaus-export-format");

      const durationFromHeader = parseNumberHeader(response.headers.get("x-videohaus-exported-duration"));
      const fallbackDuration =
        extractionMode === "range" ? Math.max(end - start, 0) : Math.max(metadata.durationSeconds, 0);

      setExtractedBlob(audioBlob);
      setExtractedFileName(nextFileName);
      setExtractedFormat(
        formatFromHeader === "mp3" || formatFromHeader === "wav" || formatFromHeader === "m4a"
          ? formatFromHeader
          : outputFormat
      );
      setExportedDurationSeconds(durationFromHeader ?? fallbackDuration);
      setExtractionProgress(100);
      setExtractionState("success");

      pushToast({
        title: "Audio extracted",
        description: `${outputFormat.toUpperCase()} export is ready to download.`,
        variant: "success"
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to extract audio.";
      setExtractionState("error");
      setExtractionProgress(0);
      setExtractionError(message);
      pushToast({
        title: "Extraction failed",
        description: message,
        variant: "error"
      });
    } finally {
      clearInterval(progressTimer);
    }
  };

  return (
    <div className="space-y-5">
      <MediaUploadDropzone
        file={selectedFile}
        onFileSelected={(file) => {
          setSelectedFile(file);
          setExtractionState("idle");
          setExtractionProgress(0);
          setExtractionError(null);
          setExtractedBlob(null);
          setExportedDurationSeconds(null);
          void loadMetadata(file);
        }}
        onClear={handleClearMedia}
        busy={metadataState === "processing"}
        validation={{
          maxSizeBytes: 1_000_000_000,
          allowedExtensions: [".mp4", ".mov", ".m4v", ".webm", ".mkv", ".avi", ".wmv", ".flv"]
        }}
        onValidationError={(message) =>
          pushToast({
            title: "Invalid file",
            description: message,
            variant: "error"
          })
        }
      />

      <ProcessingStatus
        state={metadataState}
        label={
          metadataState === "processing"
            ? "Loading source metadata..."
            : metadataState === "success"
              ? "Metadata loaded"
              : metadataState === "error"
                ? "Metadata failed"
                : "Awaiting upload"
        }
        details={metadataError ?? undefined}
        progress={metadataState === "processing" ? 35 : metadataState === "success" ? 100 : 0}
      />

      {metadataState === "error" && metadataError ? (
        <ErrorState
          title="Metadata Error"
          description={metadataError}
          actionLabel="Retry"
          onAction={() => {
            if (selectedFile) {
              void loadMetadata(selectedFile);
            }
          }}
        />
      ) : null}

      {metadata ? (
        <>
          <MetadataPanel title="Source Metadata" items={metadataItems} columns={3} />

          <VideoPlayerPreview src={sourceObjectUrl} />

          <ExportPanel
            title="Audio Extraction"
            description="Extract full-track audio or isolate a precise range with timecode controls."
            primaryAction={{
              label: "Extract Audio",
              onClick: () => {
                void handleExtractAudio();
              },
              disabled: !canExtract || extractionState === "processing",
              loading: extractionState === "processing"
            }}
          >
            <div className="space-y-4">
              <div className="grid gap-3 sm:grid-cols-2">
                <div className="space-y-2">
                  <p className="text-xs uppercase tracking-wide text-slate-400">Mode</p>
                  <div className="grid grid-cols-2 gap-2 rounded-xl border border-white/15 bg-slate-900/60 p-1">
                    <Button
                      variant={extractionMode === "full" ? "secondary" : "ghost"}
                      size="sm"
                      className="w-full"
                      onClick={() => setExtractionMode("full")}
                    >
                      Full Audio
                    </Button>
                    <Button
                      variant={extractionMode === "range" ? "secondary" : "ghost"}
                      size="sm"
                      className="w-full"
                      onClick={() => setExtractionMode("range")}
                    >
                      Selected Range
                    </Button>
                  </div>
                </div>
                <div className="space-y-2">
                  <p className="text-xs uppercase tracking-wide text-slate-400">Format</p>
                  <Select value={outputFormat} onValueChange={(value) => setOutputFormat(value as AudioOutputFormat)}>
                    <SelectTrigger className="w-full border-white/20 bg-slate-950">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectGroup>
                        <SelectItem value="mp3">MP3</SelectItem>
                        <SelectItem value="wav">WAV</SelectItem>
                        <SelectItem value="m4a">M4A / AAC</SelectItem>
                      </SelectGroup>
                    </SelectContent>
                  </Select>
                </div>
              </div>

              {extractionMode === "range" ? (
                <>
                  <DualRangeSlider
                    min={0}
                    max={Math.max(metadata.durationSeconds, minimumGapSeconds)}
                    start={start}
                    end={end}
                    minimumGap={minimumGapSeconds}
                    onChange={(nextStart, nextEnd) => setRange(nextStart, nextEnd)}
                    startLabel="Start"
                    endLabel="End"
                  />

                  <div className="grid gap-3 sm:grid-cols-2">
                    <div className="space-y-2">
                      <p className="text-xs uppercase tracking-wide text-slate-400">Start Time</p>
                      <Input
                        value={startInput}
                        onChange={(event) => setStartInput(event.target.value)}
                        onBlur={() => {
                          commitStartInput(startInput);
                        }}
                        onKeyDown={(event) => {
                          if (event.key === "Enter") {
                            commitStartInput(startInput);
                          }
                        }}
                        className="border-white/20 bg-slate-900/70 text-slate-100"
                      />
                    </div>
                    <div className="space-y-2">
                      <p className="text-xs uppercase tracking-wide text-slate-400">End Time</p>
                      <Input
                        value={endInput}
                        onChange={(event) => setEndInput(event.target.value)}
                        onBlur={() => {
                          commitEndInput(endInput);
                        }}
                        onKeyDown={(event) => {
                          if (event.key === "Enter") {
                            commitEndInput(endInput);
                          }
                        }}
                        className="border-white/20 bg-slate-900/70 text-slate-100"
                      />
                    </div>
                  </div>

                  <p className="text-xs text-slate-400">
                    Selected duration: {formatSecondsAsClock(Math.max(end - start, 0))}
                  </p>
                </>
              ) : (
                <p className="rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-slate-300">
                  Full-track extraction uses the entire audio stream from start to finish.
                </p>
              )}

              {rangeValidationMessage ? (
                <p className="rounded-lg border border-red-300/30 bg-red-500/10 px-3 py-2 text-xs text-red-100">
                  {rangeValidationMessage}
                </p>
              ) : null}
            </div>
          </ExportPanel>

          <WaveformPlaceholder
            durationSeconds={Math.max(metadata.durationSeconds, minimumGapSeconds)}
            mode={extractionMode}
            startSeconds={clampNumber(start, 0, metadata.durationSeconds)}
            endSeconds={clampNumber(end, 0, metadata.durationSeconds)}
          />

          <ProcessingStatus
            state={extractionState}
            label={
              extractionState === "processing"
                ? "Extracting audio..."
                : extractionState === "success"
                  ? "Audio ready"
                  : extractionState === "error"
                    ? "Extraction failed"
                    : "No extraction yet"
            }
            details={extractionError ?? undefined}
            progress={extractionProgress}
          />

          {extractedObjectUrl ? (
            <section className="glass-panel rounded-2xl border border-white/15 p-5">
              <div className="flex flex-wrap items-start justify-between gap-3">
                <div>
                  <h3 className="font-[var(--font-space-grotesk)] text-lg font-medium text-white">
                    Extracted Audio
                  </h3>
                  <p className="mt-1 text-sm text-slate-300">
                    Duration:{" "}
                    {formatSecondsAsClock(
                      exportedDurationSeconds ??
                        (extractionMode === "range" ? Math.max(end - start, 0) : metadata.durationSeconds)
                    )}
                  </p>
                  {extractedBlob ? (
                    <p className="mt-1 text-xs text-slate-400">File size: {formatBytes(extractedBlob.size)}</p>
                  ) : null}
                </div>
                <Button
                  render={
                    <a href={extractedObjectUrl} download={extractedFileName}>
                      <Download data-icon="inline-start" />
                      Download {(extractedFormat ?? outputFormat).toUpperCase()}
                    </a>
                  }
                />
              </div>

              <audio className="mt-4 w-full" src={extractedObjectUrl} controls preload="metadata" />
            </section>
          ) : (
            <EmptyState
              title="No Audio Export Yet"
              description="Extract audio to preview and download your result."
              icon={Waves}
            />
          )}
        </>
      ) : null}

      <SimpleToastStack toasts={toasts} onDismiss={dismissToast} />
    </div>
  );
}
