"use client";

import { useEffect, useMemo, useState } from "react";
import Image from "next/image";
import { Download } from "lucide-react";

import {
  ExportPanel,
  MediaUploadDropzone,
  MetadataPanel,
  ProcessingStatus,
  TimelineScrubber,
  VideoPlayerPreview
} from "@/components/media";
import { ErrorState } from "@/components/shared/error-state";
import { SimpleToastStack } from "@/components/shared/simple-toast-stack";
import { Button } from "@/components/ui/button";
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 { parseApiErrorMessage, parseNumberHeader } from "@/lib/client/api-utils";
import { formatBytes } from "@/lib/client/file-validation";
import {
  clampNumber,
  formatSecondsAsClock,
  formatSecondsAsTimecode
} from "@/lib/client/time-format";
import type { ApiSuccessResponse } from "@/types/api";
import type { MetadataItem } from "@/types/media-ui";
import type {
  FrameOutputFormat,
  VideoMetadata
} from "@/types/media-processing";

export function ClipFrameExtractorTool() {
  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 [currentTimeSeconds, setCurrentTimeSeconds] = useState(0);
  const [outputFormat, setOutputFormat] = useState<FrameOutputFormat>("png");

  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>("frame.png");
  const [extractedWidth, setExtractedWidth] = useState<number | null>(null);
  const [extractedHeight, setExtractedHeight] = useState<number | null>(null);
  const [extractedTimestamp, setExtractedTimestamp] = useState<number | null>(null);
  const extractedObjectUrl = useObjectUrl(extractedBlob);

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

  useEffect(() => {
    if (!selectedFile) {
      setMetadata(null);
      setMetadataState("idle");
      setMetadataError(null);
      setCurrentTimeSeconds(0);
      return;
    }

    const abortController = new AbortController();

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

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

        const response = await fetch("/api/tools/clip-frame-extractor/metadata", {
          method: "POST",
          body: formData,
          signal: abortController.signal
        });

        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.");
        }

        setMetadata(payload.data);
        setMetadataState("success");
        setCurrentTimeSeconds((previousTime) =>
          clampNumber(previousTime, 0, Math.max(payload.data.durationSeconds - 0.001, 0))
        );
      } catch (error) {
        if (abortController.signal.aborted) {
          return;
        }

        const message = error instanceof Error ? error.message : "Failed to load metadata.";
        setMetadataState("error");
        setMetadataError(message);
        pushToast({
          title: "Metadata failed",
          description: message,
          variant: "error"
        });
      }
    };

    void loadMetadata();

    return () => {
      abortController.abort();
    };
  }, [pushToast, selectedFile]);

  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",
        value: metadata.hasAudio ? "Present" : "None"
      },
      {
        key: "videoCodec",
        label: "Video Codec",
        value: metadata.videoCodec ?? "Unknown"
      }
    ];
  }, [metadata]);

  const handleClearMedia = () => {
    setSelectedFile(null);
    setMetadata(null);
    setMetadataState("idle");
    setMetadataError(null);
    setCurrentTimeSeconds(0);
    setExtractionState("idle");
    setExtractionProgress(0);
    setExtractionError(null);
    setExtractedBlob(null);
    setExtractedFileName("frame.png");
    setExtractedWidth(null);
    setExtractedHeight(null);
    setExtractedTimestamp(null);
  };

  const handleStepFrame = (direction: -1 | 1) => {
    const duration = metadata?.durationSeconds || 0;
    const frameRate = metadata?.frameRate && metadata.frameRate > 0 ? metadata.frameRate : 30;
    const frameStep = 1 / frameRate;

    setCurrentTimeSeconds((previousTime) =>
      clampNumber(previousTime + direction * frameStep, 0, Math.max(duration - 0.001, 0))
    );
  };

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

    setExtractionState("processing");
    setExtractionProgress(12);
    setExtractionError(null);
    setExtractedBlob(null);
    setExtractedWidth(null);
    setExtractedHeight(null);
    setExtractedTimestamp(null);

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

    try {
      const formData = new FormData();
      formData.set("file", selectedFile);
      formData.set("timestampSeconds", String(currentTimeSeconds));
      formData.set("format", outputFormat);

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

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

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

      const frameFileName =
        response.headers.get("x-videohaus-file-name") ||
        `frame-${currentTimeSeconds.toFixed(3)}.${outputFormat}`;

      setExtractedBlob(frameBlob);
      setExtractedFileName(frameFileName);
      setExtractedWidth(parseNumberHeader(response.headers.get("x-videohaus-width")));
      setExtractedHeight(parseNumberHeader(response.headers.get("x-videohaus-height")));
      setExtractedTimestamp(parseNumberHeader(response.headers.get("x-videohaus-timestamp")));
      setExtractionProgress(100);
      setExtractionState("success");

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

  const isReadyForExtraction =
    Boolean(selectedFile) && Boolean(metadata) && metadataState === "success";

  return (
    <div className="space-y-5">
      <MediaUploadDropzone
        file={selectedFile}
        onFileSelected={(file) => {
          setSelectedFile(file);
          setExtractionState("idle");
          setExtractionProgress(0);
          setExtractionError(null);
          setExtractedBlob(null);
        }}
        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 video 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) {
              setSelectedFile(new File([selectedFile], selectedFile.name, { type: selectedFile.type }));
            }
          }}
        />
      ) : null}

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

          <VideoPlayerPreview
            src={sourceObjectUrl}
            currentTimeSeconds={currentTimeSeconds}
            onDurationAvailable={(durationSeconds) => {
              if (!metadata.durationSeconds || metadata.durationSeconds <= 0) {
                setCurrentTimeSeconds((previousTime) =>
                  clampNumber(previousTime, 0, Math.max(durationSeconds - 0.001, 0))
                );
              }
            }}
            onTimeUpdate={(timeSeconds) => {
              setCurrentTimeSeconds(
                clampNumber(timeSeconds, 0, Math.max(metadata.durationSeconds - 0.001, 0))
              );
            }}
          />

          <TimelineScrubber
            durationSeconds={metadata.durationSeconds}
            currentTimeSeconds={currentTimeSeconds}
            onSeek={(seconds) =>
              setCurrentTimeSeconds(clampNumber(seconds, 0, Math.max(metadata.durationSeconds - 0.001, 0)))
            }
            onStepFrame={handleStepFrame}
            disabled={!isReadyForExtraction}
          />

          <ExportPanel
            title="Frame Export"
            description="Export the exact selected frame at source resolution."
            primaryAction={{
              label: "Extract Frame",
              onClick: () => {
                void handleExtractFrame();
              },
              disabled: !isReadyForExtraction || extractionState === "processing",
              loading: extractionState === "processing"
            }}
          >
            <div className="grid gap-3 sm:grid-cols-2">
              <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 FrameOutputFormat)}
                >
                  <SelectTrigger className="w-full border-white/20 bg-slate-950">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectGroup>
                      <SelectItem value="png">PNG</SelectItem>
                      <SelectItem value="jpg">JPG</SelectItem>
                    </SelectGroup>
                  </SelectContent>
                </Select>
              </div>
              <div className="space-y-2">
                <p className="text-xs uppercase tracking-wide text-slate-400">Current Timecode</p>
                <div className="rounded-lg border border-white/15 bg-slate-900/70 px-3 py-2 text-sm text-slate-100">
                  {formatSecondsAsTimecode(currentTimeSeconds)}
                </div>
              </div>
            </div>
          </ExportPanel>

          <ProcessingStatus
            state={extractionState}
            label={
              extractionState === "processing"
                ? "Extracting frame..."
                : extractionState === "success"
                  ? "Frame 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 Frame Preview
                  </h3>
                  <p className="mt-1 text-sm text-slate-300">
                    {extractedTimestamp != null
                      ? `Captured at ${formatSecondsAsTimecode(extractedTimestamp)}`
                      : `Captured at ${formatSecondsAsTimecode(currentTimeSeconds)}`}
                  </p>
                  {extractedWidth && extractedHeight ? (
                    <p className="mt-1 text-xs text-slate-400">
                      Resolution: {extractedWidth} x {extractedHeight} (source)
                    </p>
                  ) : null}
                  {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 {outputFormat.toUpperCase()}
                    </a>
                  }
                />
              </div>

              <div className="mt-4 overflow-hidden rounded-xl border border-white/10 bg-black/80">
                <Image
                  src={extractedObjectUrl}
                  alt="Extracted frame preview"
                  width={extractedWidth || metadata.width}
                  height={extractedHeight || metadata.height}
                  className="h-auto w-full object-contain"
                  unoptimized
                />
              </div>
            </section>
          ) : null}
        </>
      ) : null}

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