import { useEffect, useMemo, useRef, useState } from "react";
import { ObjectLifecycleSequence } from "@/types/timeline";
import { LifecycleIcon } from "@/components/overlay/detail/ObjectLifecycle";
import { getLifecycleItemDescription } from "@/utils/lifecycleUtil";
import { useDetailStream } from "@/context/detail-stream-context";
import scrollIntoView from "scroll-into-view-if-needed";
import useUserInteraction from "@/hooks/use-user-interaction";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { useTranslation } from "react-i18next";
import AnnotationOffsetSlider from "@/components/overlay/detail/AnnotationOffsetSlider";
import { FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr";
import ActivityIndicator from "../indicators/activity-indicator";
import { Event } from "@/types/event";
import { getIconForLabel } from "@/utils/iconUtil";
import { ReviewSegment, REVIEW_PADDING } from "@/types/review";
import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
} from "@/components/ui/collapsible";
import { LuChevronUp, LuChevronDown } from "react-icons/lu";
import { getTranslatedLabel } from "@/utils/i18n";
import EventMenu from "@/components/timeline/EventMenu";
import { FrigatePlusDialog } from "@/components/overlay/dialog/FrigatePlusDialog";
import { cn } from "@/lib/utils";

type DetailStreamProps = {
  reviewItems?: ReviewSegment[];
  currentTime: number;
  onSeek: (timestamp: number) => void;
};

export default function DetailStream({
  reviewItems,
  currentTime,
  onSeek,
}: DetailStreamProps) {
  const { data: config } = useSWR<FrigateConfig>("config");
  const { t } = useTranslation("views/events");
  const { annotationOffset } = useDetailStream();
  const scrollRef = useRef<HTMLDivElement>(null);

  const [activeReviewId, setActiveReviewId] = useState<string | undefined>(
    undefined,
  );
  const { userInteracting, setProgrammaticScroll } = useUserInteraction({
    elementRef: scrollRef,
  });

  const effectiveTime = currentTime + annotationOffset / 1000;
  const PAD = 0; // REVIEW_PADDING ?? 2;
  const [upload, setUpload] = useState<Event | undefined>(undefined);

  // Ensure we initialize the active review when reviewItems first arrive.
  // This helps when the component mounts while the video is already
  // playing — it guarantees the matching review is highlighted right
  // away instead of waiting for a future effectiveTime change.
  useEffect(() => {
    if (!reviewItems || reviewItems.length === 0) return;
    if (activeReviewId) return;

    let target: ReviewSegment | undefined;
    let closest: { r: ReviewSegment; diff: number } | undefined;

    for (const r of reviewItems) {
      const start = (r.start_time ?? 0) - PAD;
      const end = (r.end_time ?? r.start_time ?? start) + PAD;
      if (effectiveTime >= start && effectiveTime <= end) {
        target = r;
        break;
      }
      const mid = (start + end) / 2;
      const diff = Math.abs(effectiveTime - mid);
      if (!closest || diff < closest.diff) closest = { r, diff };
    }

    if (!target && closest) target = closest.r;

    if (target) {
      const start = (target.start_time ?? 0) - PAD;
      setActiveReviewId(
        `review-${target.id ?? target.start_time ?? Math.floor(start)}`,
      );
    }
  }, [reviewItems, activeReviewId, effectiveTime, PAD]);

  // Auto-scroll to current time
  useEffect(() => {
    if (!scrollRef.current || userInteracting) return;
    // Prefer the review whose range contains the effectiveTime. If none
    // contains it, pick the nearest review (by mid-point distance). This is
    // robust to unordered reviewItems and avoids always picking the last
    // element.
    const items = reviewItems ?? [];
    if (items.length === 0) return;

    let target: ReviewSegment | undefined;
    let closest: { r: ReviewSegment; diff: number } | undefined;

    for (const r of items) {
      const start = (r.start_time ?? 0) - PAD;
      const end = (r.end_time ?? r.start_time ?? start) + PAD;
      if (effectiveTime >= start && effectiveTime <= end) {
        target = r;
        break;
      }
      const mid = (start + end) / 2;
      const diff = Math.abs(effectiveTime - mid);
      if (!closest || diff < closest.diff) closest = { r, diff };
    }

    if (!target && closest) target = closest.r;

    if (target) {
      const start = (target.start_time ?? 0) - PAD;
      const id = `review-${target.id ?? target.start_time ?? Math.floor(start)}`;
      const element = scrollRef.current.querySelector(
        `[data-review-id="${id}"]`,
      ) as HTMLElement;
      if (element) {
        setProgrammaticScroll();
        scrollIntoView(element, {
          scrollMode: "if-needed",
          behavior: "smooth",
        });
      }
    }
  }, [
    reviewItems,
    effectiveTime,
    annotationOffset,
    userInteracting,
    setProgrammaticScroll,
    PAD,
  ]);

  // Auto-select active review based on effectiveTime (if inside a review range)
  useEffect(() => {
    if (!reviewItems || reviewItems.length === 0) return;
    for (const r of reviewItems) {
      const start = (r.start_time ?? 0) - PAD;
      const end = (r.end_time ?? r.start_time ?? start) + PAD;
      if (effectiveTime >= start && effectiveTime <= end) {
        setActiveReviewId(
          `review-${r.id ?? r.start_time ?? Math.floor(start)}`,
        );
        return;
      }
    }
  }, [effectiveTime, reviewItems, PAD]);

  if (!config) {
    return <ActivityIndicator />;
  }

  return (
    <div className="relative">
      <FrigatePlusDialog
        upload={upload}
        onClose={() => setUpload(undefined)}
        onEventUploaded={() => setUpload(undefined)}
      />

      <div
        ref={scrollRef}
        className="scrollbar-container h-[calc(100vh-70px)] overflow-y-auto bg-secondary"
      >
        <div className="space-y-2 p-4">
          {reviewItems?.length === 0 ? (
            <div className="py-8 text-center text-muted-foreground">
              {t("detail.noDataFound")}
            </div>
          ) : (
            reviewItems?.map((review: ReviewSegment) => {
              const id = `review-${review.id ?? review.start_time ?? Math.floor((review.start_time ?? 0) - PAD)}`;
              return (
                <ReviewGroup
                  key={id}
                  id={id}
                  review={review}
                  config={config}
                  onSeek={onSeek}
                  effectiveTime={effectiveTime}
                  isActive={activeReviewId == id}
                  onActivate={() => setActiveReviewId(id)}
                  onOpenUpload={(e) => setUpload(e)}
                />
              );
            })
          )}
        </div>
      </div>

      <AnnotationOffsetSlider />
    </div>
  );
}

type ReviewGroupProps = {
  review: ReviewSegment;
  id: string;
  config: FrigateConfig;
  onSeek: (timestamp: number) => void;
  isActive?: boolean;
  onActivate?: () => void;
  onOpenUpload?: (e: Event) => void;
  effectiveTime?: number;
};

function ReviewGroup({
  review,
  id,
  config,
  onSeek,
  isActive = false,
  onActivate,
  onOpenUpload,
  effectiveTime,
}: ReviewGroupProps) {
  const { t } = useTranslation("views/events");
  const PAD = REVIEW_PADDING ?? 2;

  // derive start timestamp from the review
  const start = (review.start_time ?? 0) - PAD;

  // display time first in the header
  const displayTime = formatUnixTimestampToDateTime(start, {
    timezone: config.ui.timezone,
    date_format:
      config.ui.time_format == "24hour"
        ? t("time.formattedTimestamp.24hour", { ns: "common" })
        : t("time.formattedTimestamp.12hour", { ns: "common" }),
    time_style: "medium",
    date_style: "medium",
  });

  const { data: fetchedEvents } = useSWR<Event[]>(
    review?.data?.detections?.length
      ? ["event_ids", { ids: review.data.detections.join(",") }]
      : null,
  );

  const rawIconLabels: string[] = fetchedEvents
    ? fetchedEvents.map((e) => e.label)
    : (review.data?.objects ?? []);

  // limit to 5 icons
  const seen = new Set<string>();
  const iconLabels: string[] = [];
  for (const lbl of rawIconLabels) {
    if (!seen.has(lbl)) {
      seen.add(lbl);
      iconLabels.push(lbl);
      if (iconLabels.length >= 5) break;
    }
  }

  const reviewInfo = useMemo(() => {
    if (review.data.metadata?.title) {
      return review.data.metadata.title;
    } else {
      const objectCount = fetchedEvents
        ? fetchedEvents.length
        : (review.data.objects ?? []).length;

      return `${objectCount} ${t("detail.trackedObject", { count: objectCount })}`;
    }
  }, [review, t, fetchedEvents]);

  return (
    <div
      data-review-id={id}
      className={`cursor-pointer rounded-lg border bg-background p-3 outline outline-[3px] -outline-offset-[2.8px] ${
        isActive
          ? "shadow-selected outline-selected"
          : "outline-transparent duration-500"
      }`}
    >
      <div
        className="flex items-center justify-between"
        onClick={() => {
          onActivate?.();
          onSeek(start);
        }}
      >
        <div className="flex items-center gap-2">
          <div className="flex flex-col">
            <div className="text-sm font-medium">{displayTime}</div>
            <div className="text-xs text-muted-foreground">{reviewInfo}</div>
          </div>
        </div>
        <div className="flex items-center gap-2">
          {iconLabels.slice(0, 5).map((lbl, idx) => (
            <span key={`${lbl}-${idx}`}>
              {getIconForLabel(lbl, "size-4 text-primary dark:text-white")}
            </span>
          ))}
        </div>
      </div>

      {isActive && (
        <div className="mt-2 space-y-2">
          {!fetchedEvents ? (
            <ActivityIndicator />
          ) : (
            fetchedEvents.map((event) => {
              return (
                <EventCollapsible
                  key={event.id}
                  event={event}
                  effectiveTime={effectiveTime}
                  onSeek={onSeek}
                  onOpenUpload={onOpenUpload}
                />
              );
            })
          )}
        </div>
      )}
    </div>
  );
}

type EventCollapsibleProps = {
  event: Event;
  effectiveTime?: number;
  onSeek: (ts: number) => void;
  onOpenUpload?: (e: Event) => void;
};
function EventCollapsible({
  event,
  effectiveTime,
  onSeek,
  onOpenUpload,
}: EventCollapsibleProps) {
  const [open, setOpen] = useState(false);
  const { t } = useTranslation("views/events");
  const { data: config } = useSWR<FrigateConfig>("config");

  const { selectedObjectId, setSelectedObjectId } = useDetailStream();

  const formattedStart = config
    ? formatUnixTimestampToDateTime(event.start_time ?? 0, {
        timezone: config.ui.timezone,
        date_format:
          config.ui.time_format == "24hour"
            ? t("time.formattedTimestampHourMinuteSecond.24hour", {
                ns: "common",
              })
            : t("time.formattedTimestampHourMinuteSecond.12hour", {
                ns: "common",
              }),
        time_style: "medium",
        date_style: "medium",
      })
    : "";

  const formattedEnd = config
    ? formatUnixTimestampToDateTime(event.end_time ?? 0, {
        timezone: config.ui.timezone,
        date_format:
          config.ui.time_format == "24hour"
            ? t("time.formattedTimestampHourMinuteSecond.24hour", {
                ns: "common",
              })
            : t("time.formattedTimestampHourMinuteSecond.12hour", {
                ns: "common",
              }),
        time_style: "medium",
        date_style: "medium",
      })
    : "";

  // Clear selectedObjectId when effectiveTime has passed this event's end_time
  useEffect(() => {
    if (selectedObjectId === event.id && effectiveTime && event.end_time) {
      if (effectiveTime > event.end_time) {
        setSelectedObjectId(undefined);
      }
    }
  }, [
    selectedObjectId,
    event.id,
    event.end_time,
    effectiveTime,
    setSelectedObjectId,
  ]);

  return (
    <Collapsible open={open} onOpenChange={(o) => setOpen(o)}>
      <div
        className={cn(
          "rounded-md bg-secondary p-2 outline outline-[3px] -outline-offset-[2.8px]",
          event.id == selectedObjectId
            ? "shadow-selected outline-selected"
            : "outline-transparent duration-500",
          event.id != selectedObjectId &&
            (effectiveTime ?? 0) >= (event.start_time ?? 0) &&
            (effectiveTime ?? 0) <= (event.end_time ?? event.start_time ?? 0) &&
            "bg-secondary-highlight/80 outline-[1px] -outline-offset-[0.8px] outline-primary/40",
        )}
      >
        <div className="flex w-full items-center justify-between">
          <div
            className="flex items-center gap-2 text-sm font-medium"
            onClick={(e) => {
              e.stopPropagation();
              onSeek(event.start_time ?? 0);
              if (event.id) setSelectedObjectId(event.id);
            }}
            role="button"
          >
            {getIconForLabel(
              event.label,
              "size-4 text-primary dark:text-white",
            )}
            <div className="flex items-end gap-2">
              <span>{getTranslatedLabel(event.label)}</span>
              <span className="text-xs text-secondary-foreground">
                {formattedStart ?? ""} - {formattedEnd ?? ""}
              </span>
            </div>
          </div>
          <div className="flex flex-1 flex-row justify-end">
            <EventMenu
              event={event}
              config={config}
              onOpenUpload={(e) => onOpenUpload?.(e)}
            />
          </div>

          <div className="flex items-center gap-2">
            <CollapsibleTrigger asChild>
              <button
                onClick={(e) => e.stopPropagation()}
                className="rounded bg-muted px-2 py-1 text-xs"
                aria-label={t("detail.aria")}
              >
                {open ? (
                  <LuChevronUp className="size-3" />
                ) : (
                  <LuChevronDown className="size-3" />
                )}
              </button>
            </CollapsibleTrigger>
          </div>
        </div>
        <CollapsibleContent>
          <div className="mt-2">
            <ObjectTimeline
              eventId={event.id}
              onSeek={(ts) => {
                onSeek(ts);
              }}
              effectiveTime={effectiveTime}
            />
          </div>
        </CollapsibleContent>
      </div>
    </Collapsible>
  );
}

type LifecycleItemProps = {
  event: ObjectLifecycleSequence;
  onSeek: (timestamp: number) => void;
  isActive?: boolean;
};

function LifecycleItem({ event, isActive }: LifecycleItemProps) {
  const { t } = useTranslation("views/events");
  const { data: config } = useSWR<FrigateConfig>("config");

  const formattedEventTimestamp = config
    ? formatUnixTimestampToDateTime(event.timestamp ?? 0, {
        timezone: config.ui.timezone,
        date_format:
          config.ui.time_format == "24hour"
            ? t("time.formattedTimestampHourMinuteSecond.24hour", {
                ns: "common",
              })
            : t("time.formattedTimestampHourMinuteSecond.12hour", {
                ns: "common",
              }),
        time_style: "medium",
        date_style: "medium",
      })
    : "";

  return (
    <div
      className={cn(
        "flex items-center gap-2 text-sm text-primary-variant",
        isActive ? "text-white" : "duration-500",
      )}
    >
      <div className="flex size-4 items-center justify-center">
        <LifecycleIcon lifecycleItem={event} className="size-3" />
      </div>
      <div className="flex w-full flex-row justify-between">
        <div>{getLifecycleItemDescription(event)}</div>
        <div className={cn("p-1 text-xs")}>{formattedEventTimestamp}</div>
      </div>
    </div>
  );
}

// Fetch and render timeline entries for a single event id on demand.
function ObjectTimeline({
  eventId,
  onSeek,
  effectiveTime,
}: {
  eventId: string;
  onSeek: (ts: number) => void;
  effectiveTime?: number;
}) {
  const { t } = useTranslation("views/events");
  const { data: timeline, isValidating } = useSWR<ObjectLifecycleSequence[]>([
    "timeline",
    {
      source_id: eventId,
    },
  ]);

  if ((!timeline || timeline.length === 0) && isValidating) {
    return <ActivityIndicator className="h-2 w-2" size={2} />;
  }

  if (!timeline || timeline.length === 0) {
    return (
      <div className="py-2 text-sm text-muted-foreground">
        {t("detail.noObjectDetailData")}
      </div>
    );
  }

  return (
    <div className="mx-2 mt-4 space-y-2">
      {timeline.map((event, idx) => {
        const isActive =
          Math.abs((effectiveTime ?? 0) - (event.timestamp ?? 0)) <= 0.5;
        return (
          <div
            key={`${event.timestamp}-${event.source_id ?? idx}`}
            onClick={() => {
              onSeek(event.timestamp);
            }}
          >
            <LifecycleItem event={event} onSeek={onSeek} isActive={isActive} />
          </div>
        );
      })}
    </div>
  );
}
