import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { isDesktop, isIOS, isMobile } from "react-device-detect";
import { FaArrowRight, FaCalendarAlt, FaCheckCircle } from "react-icons/fa";
import { MdOutlineRestartAlt, MdUndo } from "react-icons/md";
import { LuHand, LuPencil } from "react-icons/lu";

import { FrigateConfig } from "@/types/frigateConfig";
import { TimeRange } from "@/types/timeline";
import { RecordingsSummary } from "@/types/review";
import { ASPECT_PORTRAIT_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";

import { Button } from "@/components/ui/button";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Drawer, DrawerContent } from "@/components/ui/drawer";
import { Label } from "@/components/ui/label";
import { Slider } from "@/components/ui/slider";
import { Switch } from "@/components/ui/switch";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { SelectSeparator } from "@/components/ui/select";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
import { TimezoneAwareCalendar } from "@/components/overlay/ReviewActivityCalendar";

import { useApiHost } from "@/api";
import {
  useFormattedTimestamp,
  use24HourTime,
  useTimezone,
} from "@/hooks/use-date-utils";
import { getUTCOffset } from "@/utils/dateUtil";
import useSWR from "swr";
import { cn } from "@/lib/utils";
import MotionSearchROICanvas from "./MotionSearchROICanvas";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";

type MotionSearchDialogProps = {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  config: FrigateConfig;
  cameras: string[];
  selectedCamera: string | null;
  onCameraSelect: (camera: string) => void;
  cameraLocked?: boolean;
  polygonPoints: number[][];
  setPolygonPoints: React.Dispatch<React.SetStateAction<number[][]>>;
  isDrawingROI: boolean;
  setIsDrawingROI: React.Dispatch<React.SetStateAction<boolean>>;
  parallelMode: boolean;
  setParallelMode: React.Dispatch<React.SetStateAction<boolean>>;
  threshold: number;
  setThreshold: React.Dispatch<React.SetStateAction<number>>;
  minArea: number;
  setMinArea: React.Dispatch<React.SetStateAction<number>>;
  maxResults: number;
  setMaxResults: React.Dispatch<React.SetStateAction<number>>;
  searchRange?: TimeRange;
  setSearchRange: React.Dispatch<React.SetStateAction<TimeRange | undefined>>;
  defaultRange: TimeRange;
  isSearching: boolean;
  canStartSearch: boolean;
  onStartSearch: () => void;
  timezone?: string;
};

export default function MotionSearchDialog({
  open,
  onOpenChange,
  config,
  cameras,
  selectedCamera,
  onCameraSelect,
  cameraLocked = false,
  polygonPoints,
  setPolygonPoints,
  isDrawingROI,
  setIsDrawingROI,
  parallelMode,
  setParallelMode,
  threshold,
  setThreshold,
  minArea,
  setMinArea,
  maxResults,
  setMaxResults,
  searchRange,
  setSearchRange,
  defaultRange,
  isSearching,
  canStartSearch,
  onStartSearch,
  timezone,
}: MotionSearchDialogProps) {
  const { t } = useTranslation(["views/motionSearch", "common"]);
  const apiHost = useApiHost();
  const [imageLoaded, setImageLoaded] = useState(false);
  const [panMode, setPanMode] = useState(false);

  const recordingsTimezone = useTimezone(config);
  const { data: recordingsSummary } = useSWR<RecordingsSummary>(
    selectedCamera
      ? [
          "recordings/summary",
          { timezone: recordingsTimezone, cameras: selectedCamera },
        ]
      : null,
  );

  const cameraConfig = useMemo(() => {
    if (!selectedCamera) return undefined;
    return config.cameras[selectedCamera];
  }, [config, selectedCamera]);

  const aspectRatio = useMemo(() => {
    if (!cameraConfig) {
      return 16 / 9;
    }

    return cameraConfig.detect.width / cameraConfig.detect.height;
  }, [cameraConfig]);

  // Determine camera aspect ratio category
  const cameraAspect = useMemo(() => {
    if (!aspectRatio) {
      return "normal";
    } else if (aspectRatio > ASPECT_WIDE_LAYOUT) {
      return "wide";
    } else if (aspectRatio < ASPECT_PORTRAIT_LAYOUT) {
      return "tall";
    } else {
      return "normal";
    }
  }, [aspectRatio]);

  const polygonClosed = useMemo(
    () => !isDrawingROI && polygonPoints.length >= 3,
    [isDrawingROI, polygonPoints.length],
  );

  const undoPolygonPoint = useCallback(() => {
    if (polygonPoints.length === 0 || isSearching) {
      return;
    }

    setPolygonPoints((prev) => prev.slice(0, -1));
    setIsDrawingROI(true);
  }, [isSearching, setIsDrawingROI, setPolygonPoints, polygonPoints.length]);

  const resetPolygon = useCallback(() => {
    if (polygonPoints.length === 0 || isSearching) {
      return;
    }

    setPolygonPoints([]);
    setIsDrawingROI(true);
  }, [isSearching, polygonPoints.length, setIsDrawingROI, setPolygonPoints]);

  useEffect(() => {
    setImageLoaded(false);
    setPanMode(false);
  }, [selectedCamera]);

  const Overlay = isDesktop ? Dialog : Drawer;
  const Content = isDesktop ? DialogContent : DrawerContent;

  return (
    <Overlay open={open} onOpenChange={onOpenChange}>
      <Content
        {...(isDesktop
          ? {
              onOpenAutoFocus: (event: Event) => event.preventDefault(),
            }
          : {})}
        className={cn(
          isDesktop
            ? "scrollbar-container max-h-[90dvh] overflow-y-auto sm:max-w-[75%]"
            : "flex max-h-[90dvh] flex-col overflow-hidden rounded-lg pb-4",
        )}
      >
        <div
          className={cn(
            !isDesktop &&
              "scrollbar-container flex min-h-0 w-full flex-col gap-4 overflow-y-auto overflow-x-hidden px-4",
          )}
        >
          <DialogHeader>
            <DialogTitle className="mt-4 md:mt-auto">
              {t("dialog.title")}
            </DialogTitle>
            <p className="my-1 text-sm text-muted-foreground">
              {t("description")}
            </p>
          </DialogHeader>
          <DialogDescription className="hidden" />
          <div
            className={cn(
              "flex gap-4",
              isDesktop ? "mt-4 flex-row" : "flex-col landscape:flex-row",
            )}
          >
            <div
              className={cn("flex flex-1 flex-col", !isDesktop && "min-w-0")}
            >
              {(!cameraLocked || !selectedCamera) && (
                <div className="flex items-end justify-between gap-2">
                  <div className="mt-2 md:min-w-64">
                    <div className="grid gap-2">
                      <Label htmlFor="motion-search-camera">
                        {t("dialog.cameraLabel")}
                      </Label>
                      <Select
                        value={selectedCamera ?? undefined}
                        onValueChange={(value) => onCameraSelect(value)}
                      >
                        <SelectTrigger id="motion-search-camera">
                          <SelectValue placeholder={t("selectCamera")} />
                        </SelectTrigger>
                        <SelectContent>
                          {cameras.map((camera) => (
                            <SelectItem
                              key={camera}
                              value={camera}
                              className="cursor-pointer hover:bg-accent hover:text-accent-foreground"
                            >
                              <CameraNameLabel camera={camera} />
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </div>
                  </div>
                </div>
              )}

              <TransformWrapper
                minScale={1.0}
                wheel={{ smoothStep: 0.005 }}
                panning={{ disabled: !isDesktop && !panMode }}
                pinch={{ disabled: !isDesktop && !panMode }}
                doubleClick={{ disabled: !isDesktop && !panMode }}
              >
                <div className="flex flex-col gap-2">
                  <TransformComponent
                    wrapperStyle={{
                      width: "100%",
                      height: isDesktop ? "100%" : "auto",
                    }}
                    contentStyle={{
                      position: "relative",
                      width: "100%",
                      height: "100%",
                    }}
                  >
                    <div
                      className={cn(
                        "relative mx-auto flex items-center justify-center overflow-hidden rounded-lg border bg-secondary",
                        cameraAspect === "tall"
                          ? "max-h-[50dvh] lg:max-h-[60dvh]"
                          : "w-full",
                      )}
                      style={{ aspectRatio }}
                    >
                      {selectedCamera && cameraConfig ? (
                        <div className="relative h-full w-full">
                          <img
                            alt={t("dialog.previewAlt", {
                              camera: selectedCamera,
                            })}
                            src={`${apiHost}api/${selectedCamera}/latest.jpg?h=500`}
                            className="h-full w-full object-contain"
                            onLoad={() => setImageLoaded(true)}
                            ref={(node) => {
                              if (node?.complete && node.naturalWidth > 0) {
                                setImageLoaded(true);
                              }
                            }}
                          />
                          {!imageLoaded && (
                            <div className="absolute inset-0 flex items-center justify-center">
                              <ActivityIndicator className="h-8 w-8" />
                            </div>
                          )}
                          <MotionSearchROICanvas
                            camera={selectedCamera}
                            width={cameraConfig.detect.width}
                            height={cameraConfig.detect.height}
                            polygonPoints={polygonPoints}
                            setPolygonPoints={setPolygonPoints}
                            isDrawing={isDrawingROI}
                            setIsDrawing={setIsDrawingROI}
                            isInteractive={true}
                            panMode={panMode}
                          />
                        </div>
                      ) : (
                        <div className="flex h-full w-full items-center justify-center text-sm text-muted-foreground">
                          {t("selectCamera")}
                        </div>
                      )}
                    </div>
                  </TransformComponent>
                </div>
              </TransformWrapper>

              {selectedCamera && (
                <div className="my-2 flex w-full flex-row justify-between rounded-md bg-background_alt p-2 text-sm">
                  <div className="my-1 inline-flex items-center">
                    {t("polygonControls.points", {
                      count: polygonPoints.length,
                    })}
                    {polygonClosed && <FaCheckCircle className="ml-2 size-5" />}
                  </div>
                  <div className="flex flex-row justify-center gap-2">
                    {!isDesktop && (
                      <Tooltip>
                        <TooltipTrigger asChild>
                          <Button
                            variant={panMode ? "select" : "default"}
                            className="size-8 rounded-md p-1.5"
                            aria-label={
                              panMode
                                ? t("polygonControls.moveMode")
                                : t("polygonControls.drawMode")
                            }
                            onClick={() => setPanMode((prev) => !prev)}
                          >
                            {panMode ? (
                              <LuHand className="text-selected-foreground" />
                            ) : (
                              <LuPencil className="text-secondary-foreground" />
                            )}
                          </Button>
                        </TooltipTrigger>
                        <TooltipContent>
                          {panMode
                            ? t("polygonControls.moveMode")
                            : t("polygonControls.drawMode")}
                        </TooltipContent>
                      </Tooltip>
                    )}
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          variant="default"
                          className={cn(
                            "rounded-md",
                            isDesktop ? "size-6 p-1" : "size-8 p-1.5",
                          )}
                          aria-label={t("polygonControls.undo")}
                          disabled={polygonPoints.length === 0 || isSearching}
                          onClick={undoPolygonPoint}
                        >
                          <MdUndo className="text-secondary-foreground" />
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent>
                        {t("polygonControls.undo")}
                      </TooltipContent>
                    </Tooltip>
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          variant="default"
                          className={cn(
                            "rounded-md",
                            isDesktop ? "size-6 p-1" : "size-8 p-1.5",
                          )}
                          aria-label={t("polygonControls.reset")}
                          disabled={polygonPoints.length === 0 || isSearching}
                          onClick={resetPolygon}
                        >
                          <MdOutlineRestartAlt className="text-secondary-foreground" />
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent>
                        {t("polygonControls.reset")}
                      </TooltipContent>
                    </Tooltip>
                  </div>
                </div>
              )}
            </div>

            <div
              className={cn(
                "flex w-full flex-col gap-4 space-y-4 lg:w-[340px]",
                isMobile && "landscape:w-[40%] landscape:flex-shrink-0",
              )}
            >
              <div className="grid gap-3">
                <h4 className="mb-4 font-medium leading-none">
                  {t("settings.title")}
                </h4>
                <div className="grid gap-4 space-y-2">
                  <div className="grid gap-2">
                    <Label htmlFor="threshold">{t("settings.threshold")}</Label>
                    <div className="flex items-center gap-2">
                      <Slider
                        id="threshold"
                        min={1}
                        max={255}
                        step={1}
                        value={[threshold]}
                        onValueChange={([value]) => setThreshold(value)}
                      />
                      <span className="w-12 text-sm">{threshold}</span>
                    </div>
                    <p className="text-xs text-muted-foreground">
                      {t("settings.thresholdDesc")}
                    </p>
                  </div>
                  <div className="grid gap-2">
                    <Label htmlFor="minArea">{t("settings.minArea")}</Label>
                    <div className="flex items-center gap-2">
                      <Slider
                        id="minArea"
                        min={1}
                        max={100}
                        step={1}
                        value={[minArea]}
                        onValueChange={([value]) => setMinArea(value)}
                      />
                      <span className="w-12 text-sm">{minArea}%</span>
                    </div>
                    <p className="text-xs text-muted-foreground">
                      {t("settings.minAreaDesc")}
                    </p>
                  </div>
                  <div className="grid gap-2">
                    <div className="flex items-center justify-between gap-2">
                      <Label htmlFor="parallelMode">
                        {t("settings.parallelMode")}
                      </Label>
                      <Switch
                        id="parallelMode"
                        checked={parallelMode}
                        onCheckedChange={setParallelMode}
                      />
                    </div>
                    <p className="text-xs text-muted-foreground">
                      {t("settings.parallelModeDesc")}
                    </p>
                  </div>
                  <div className="grid gap-2">
                    <Label htmlFor="maxResults">
                      {t("settings.maxResults")}
                    </Label>
                    <div className="flex items-center gap-2">
                      <Slider
                        id="maxResults"
                        min={1}
                        max={200}
                        step={1}
                        value={[maxResults]}
                        onValueChange={([value]) => setMaxResults(value)}
                      />
                      <span className="w-12 text-sm">{maxResults}</span>
                    </div>
                    <p className="text-xs text-muted-foreground">
                      {t("settings.maxResultsDesc")}
                    </p>
                  </div>
                </div>
              </div>

              <SearchRangeSelector
                range={searchRange}
                setRange={setSearchRange}
                defaultRange={defaultRange}
                timezone={timezone}
                recordingsSummary={recordingsSummary}
              />

              <Button
                className="w-full"
                variant="select"
                onClick={onStartSearch}
                disabled={!canStartSearch || isSearching}
              >
                {t("startSearch")}
              </Button>
            </div>
          </div>
        </div>
      </Content>
    </Overlay>
  );
}

type SearchRangeSelectorProps = {
  range?: TimeRange;
  setRange: React.Dispatch<React.SetStateAction<TimeRange | undefined>>;
  defaultRange: TimeRange;
  timezone?: string;
  recordingsSummary?: RecordingsSummary;
};

function SearchRangeSelector({
  range,
  setRange,
  defaultRange,
  timezone,
  recordingsSummary,
}: SearchRangeSelectorProps) {
  const { t } = useTranslation(["views/motionSearch", "common"]);
  const [startOpen, setStartOpen] = useState(false);
  const [endOpen, setEndOpen] = useState(false);

  const timezoneOffset = useMemo(
    () =>
      timezone ? Math.round(getUTCOffset(new Date(), timezone)) : undefined,
    [timezone],
  );
  const localTimeOffset = useMemo(
    () =>
      Math.round(
        getUTCOffset(
          new Date(),
          Intl.DateTimeFormat().resolvedOptions().timeZone,
        ),
      ),
    [],
  );

  const startTime = useMemo(() => {
    let time = range?.after ?? defaultRange.after;

    if (timezoneOffset !== undefined) {
      time = time + (timezoneOffset - localTimeOffset) * 60;
    }

    return time;
  }, [range, defaultRange, timezoneOffset, localTimeOffset]);

  const endTime = useMemo(() => {
    let time = range?.before ?? defaultRange.before;

    if (timezoneOffset !== undefined) {
      time = time + (timezoneOffset - localTimeOffset) * 60;
    }

    return time;
  }, [range, defaultRange, timezoneOffset, localTimeOffset]);

  const { data: config } = useSWR<FrigateConfig>("config");
  const is24Hour = use24HourTime(config);

  const formattedStart = useFormattedTimestamp(
    startTime,
    is24Hour
      ? t("time.formattedTimestamp.24hour", { ns: "common" })
      : t("time.formattedTimestamp.12hour", { ns: "common" }),
  );
  const formattedEnd = useFormattedTimestamp(
    endTime,
    is24Hour
      ? t("time.formattedTimestamp.24hour", { ns: "common" })
      : t("time.formattedTimestamp.12hour", { ns: "common" }),
  );

  const startClock = useMemo(() => {
    const date = new Date(startTime * 1000);
    return `${date.getHours().toString().padStart(2, "0")}:${date
      .getMinutes()
      .toString()
      .padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
  }, [startTime]);

  const endClock = useMemo(() => {
    const date = new Date(endTime * 1000);
    return `${date.getHours().toString().padStart(2, "0")}:${date
      .getMinutes()
      .toString()
      .padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
  }, [endTime]);

  return (
    <div className="grid gap-2">
      <Label>{t("timeRange.title")}</Label>
      <div className="flex items-center rounded-lg bg-secondary px-2 py-1 text-secondary-foreground">
        <FaCalendarAlt />
        <div className="flex flex-wrap items-center">
          <Popover
            open={startOpen}
            onOpenChange={(open) => {
              if (!open) {
                setStartOpen(false);
              }
            }}
          >
            <PopoverTrigger asChild>
              <Button
                className="text-primary"
                aria-label={t("timeRange.start")}
                variant={startOpen ? "select" : "default"}
                size="sm"
                onClick={() => {
                  setStartOpen(true);
                  setEndOpen(false);
                }}
              >
                {formattedStart}
              </Button>
            </PopoverTrigger>
            <PopoverContent className="flex flex-col items-center">
              <TimezoneAwareCalendar
                timezone={timezone}
                recordingsSummary={recordingsSummary}
                selectedDay={new Date(startTime * 1000)}
                onSelect={(day) => {
                  if (!day) {
                    return;
                  }

                  setRange({
                    before: endTime,
                    after: day.getTime() / 1000 + 1,
                  });
                }}
              />
              <SelectSeparator className="bg-secondary" />
              <input
                className="mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
                id="startTime"
                type="time"
                value={startClock}
                step={isIOS ? "60" : "1"}
                onChange={(e) => {
                  const clock = e.target.value;
                  const [hour, minute, second] = isIOS
                    ? [...clock.split(":"), "00"]
                    : clock.split(":");

                  const start = new Date(startTime * 1000);
                  start.setHours(
                    parseInt(hour),
                    parseInt(minute),
                    parseInt(second ?? 0),
                    0,
                  );
                  setRange({
                    before: endTime,
                    after: start.getTime() / 1000,
                  });
                }}
              />
            </PopoverContent>
          </Popover>
          <FaArrowRight className="size-4 text-primary" />
          <Popover
            open={endOpen}
            onOpenChange={(open) => {
              if (!open) {
                setEndOpen(false);
              }
            }}
          >
            <PopoverTrigger asChild>
              <Button
                className="text-primary"
                aria-label={t("timeRange.end")}
                variant={endOpen ? "select" : "default"}
                size="sm"
                onClick={() => {
                  setEndOpen(true);
                  setStartOpen(false);
                }}
              >
                {formattedEnd}
              </Button>
            </PopoverTrigger>
            <PopoverContent className="flex flex-col items-center">
              <TimezoneAwareCalendar
                timezone={timezone}
                recordingsSummary={recordingsSummary}
                selectedDay={new Date(endTime * 1000)}
                onSelect={(day) => {
                  if (!day) {
                    return;
                  }

                  setRange({
                    after: startTime,
                    before: day.getTime() / 1000,
                  });
                }}
              />
              <SelectSeparator className="bg-secondary" />
              <input
                className="mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
                id="endTime"
                type="time"
                value={endClock}
                step={isIOS ? "60" : "1"}
                onChange={(e) => {
                  const clock = e.target.value;
                  const [hour, minute, second] = isIOS
                    ? [...clock.split(":"), "00"]
                    : clock.split(":");

                  const end = new Date(endTime * 1000);
                  end.setHours(
                    parseInt(hour),
                    parseInt(minute),
                    parseInt(second ?? 0),
                    0,
                  );
                  setRange({
                    before: end.getTime() / 1000,
                    after: startTime,
                  });
                }}
              />
            </PopoverContent>
          </Popover>
        </div>
      </div>
    </div>
  );
}
