'use client';

import { useEffect, useMemo, useState } from 'react';
import dynamic from 'next/dynamic';
import { AlertTriangle, MapPinned, RefreshCw } from 'lucide-react';

import { getDefectVisual } from '@/constants/defectVisualConfig';
import type {
  DetectionCoordinateClass,
  DetectionCoordinateItem,
  DetectionCoordinatesResponse,
} from '@/types';
import { Button } from '@/components/ui/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';

import type { AssignmentRangePoint } from './assignmentRange';
import { AssignmentRangeActions } from './AssignmentRangeActions';

type AssignmentIssuesMapProps = {
  data?: DetectionCoordinatesResponse;
  isLoading: boolean;
  isFetchingMore: boolean;
  isError: boolean;
  hasMore: boolean;
  startPoint: AssignmentRangePoint | null;
  endPoint: AssignmentRangePoint | null;
  onSetStart: (point: AssignmentRangePoint) => void;
  onSetEnd: (point: AssignmentRangePoint) => void;
  onLoadMore: () => void;
  onRetry: () => void;
};

type ClientAssignmentIssuesMapProps = {
  items: DetectionCoordinateItem[];
  classes: DetectionCoordinateClass[];
  selectedDetectionId: number | null;
  startPoint: AssignmentRangePoint | null;
  endPoint: AssignmentRangePoint | null;
  onSelectDetection: (detectionId: number) => void;
  onSetStart: (point: AssignmentRangePoint) => void;
  onSetEnd: (point: AssignmentRangePoint) => void;
};

function isValidCoordinate(item: DetectionCoordinateItem) {
  return (
    Number.isFinite(item.latitude) &&
    Number.isFinite(item.longitude) &&
    item.latitude >= -90 &&
    item.latitude <= 90 &&
    item.longitude >= -180 &&
    item.longitude <= 180
  );
}

const ClientAssignmentIssuesMap = dynamic<ClientAssignmentIssuesMapProps>(
  async () => {
    const { CircleMarker, MapContainer, Popup, TileLayer, Tooltip, useMap } =
      await import('react-leaflet');
    const { canvas } = await import('leaflet');

    function FitMapToIssues({ items }: { items: DetectionCoordinateItem[] }) {
      const map = useMap();

      useEffect(() => {
        if (items.length === 0) return;

        if (items.length === 1) {
          map.setView([items[0].latitude, items[0].longitude], 17);
          return;
        }

        map.fitBounds(
          items.map(
            (item) => [item.latitude, item.longitude] as [number, number],
          ),
          {
            padding: [32, 32],
            maxZoom: 17,
          },
        );
      }, [items, map]);

      return null;
    }

    return function ClientAssignmentIssuesMapInner({
      items,
      classes,
      selectedDetectionId,
      startPoint,
      endPoint,
      onSelectDetection,
      onSetStart,
      onSetEnd,
    }: ClientAssignmentIssuesMapProps) {
      const markerRenderer = useMemo(() => canvas({ tolerance: 12 }), []);
      const displayNames = new Map(
        classes.map((item) => [item.class_name, item.display_name]),
      );
      const initialCenter: [number, number] = [
        items[0].latitude,
        items[0].longitude,
      ];

      return (
        <MapContainer
          center={initialCenter}
          zoom={15}
          scrollWheelZoom
          className="h-full w-full"
        >
          <TileLayer
            attribution="&copy; OpenStreetMap contributors"
            url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          <FitMapToIssues items={items} />

          {items.map((item) => {
            const visual = getDefectVisual(item.class_name);
            const IssueIcon = visual.icon;
            const isSelected = selectedDetectionId === item.id;
            const isStart = startPoint?.detectionId === item.id;
            const isEnd = endPoint?.detectionId === item.id;
            const rangeLabel = isStart ? 'A' : isEnd ? 'B' : null;
            const rangeColor = isStart
              ? '#16a34a'
              : isEnd
                ? '#dc2626'
                : visual.boundingBoxColor;
            const isRangeIssue = isStart || isEnd;
            const displayName =
              displayNames.get(item.class_name) ??
              item.class_name.replaceAll('_', ' ');
            const point: AssignmentRangePoint = {
              detectionId: item.id,
              latitude: item.latitude,
              longitude: item.longitude,
            };

            return (
              <CircleMarker
                key={item.id}
                center={[item.latitude, item.longitude]}
                renderer={markerRenderer}
                radius={isSelected || isRangeIssue ? 11 : 8}
                pathOptions={{
                  color:
                    isSelected || isRangeIssue
                      ? '#ffffff'
                      : visual.boundingBoxColor,
                  fillColor: rangeColor,
                  fillOpacity: isSelected || isRangeIssue ? 1 : 0.82,
                  weight: isSelected || isRangeIssue ? 4 : 2,
                }}
                eventHandlers={{
                  click: () => onSelectDetection(item.id),
                }}
              >
                <Popup minWidth={240} maxWidth={280}>
                  <div className="w-60">
                    <div className="flex items-start gap-3 border-b pb-3 pr-4">
                      <span
                        className="flex size-9 shrink-0 items-center justify-center rounded-full"
                        style={{
                          backgroundColor: `${visual.boundingBoxColor}1f`,
                          color: visual.boundingBoxColor,
                        }}
                      >
                        <IssueIcon className="size-4" />
                      </span>
                      <div className="min-w-0 flex-1">
                        <p className="!m-0 truncate text-sm font-semibold leading-5">
                          {displayName}
                        </p>
                        <p className="!m-0 text-xs leading-4 text-muted-foreground">
                          Detection #{item.id}
                        </p>
                      </div>
                      {rangeLabel ? (
                        <span
                          className="flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold text-white"
                          style={{ backgroundColor: rangeColor }}
                          aria-label={isStart ? 'Start issue' : 'End issue'}
                        >
                          {rangeLabel}
                        </span>
                      ) : null}
                    </div>

                    <dl className="!m-0 space-y-2 py-3 text-sm">
                      <div className="flex items-center justify-between gap-4">
                        <dt className="text-muted-foreground">Confidence</dt>
                        <dd className="!m-0 font-medium tabular-nums">
                          {Math.round(item.confidence * 100)}%
                        </dd>
                      </div>
                      <div className="flex items-center justify-between gap-4">
                        <dt className="text-muted-foreground">Latitude</dt>
                        <dd className="!m-0 font-mono text-xs tabular-nums">
                          {item.latitude.toFixed(6)}
                        </dd>
                      </div>
                      <div className="flex items-center justify-between gap-4">
                        <dt className="text-muted-foreground">Longitude</dt>
                        <dd className="!m-0 font-mono text-xs tabular-nums">
                          {item.longitude.toFixed(6)}
                        </dd>
                      </div>
                    </dl>

                    <div className="border-t pt-3">
                      <AssignmentRangeActions
                        point={point}
                        startPoint={startPoint}
                        endPoint={endPoint}
                        onSetStart={onSetStart}
                        onSetEnd={onSetEnd}
                        stretch
                      />
                    </div>
                  </div>
                </Popup>
                {rangeLabel ? (
                  <Tooltip permanent direction="center" opacity={1}>
                    {rangeLabel}
                  </Tooltip>
                ) : null}
              </CircleMarker>
            );
          })}
        </MapContainer>
      );
    };
  },
  {
    ssr: false,
    loading: () => <div className="size-full animate-pulse bg-muted" />,
  },
);

export function AssignmentIssuesMap({
  data,
  isLoading,
  isFetchingMore,
  isError,
  hasMore,
  startPoint,
  endPoint,
  onSetStart,
  onSetEnd,
  onLoadMore,
  onRetry,
}: AssignmentIssuesMapProps) {
  const [selectedDetectionId, setSelectedDetectionId] = useState<number | null>(
    null,
  );
  const validItems = useMemo(
    () => (data?.items ?? []).filter(isValidCoordinate),
    [data?.items],
  );
  const total = data?.total ?? 0;
  const loadedCount = Math.min(data?.items.length ?? 0, total);

  useEffect(() => {
    if (
      selectedDetectionId !== null &&
      !validItems.some((item) => item.id === selectedDetectionId)
    ) {
      setSelectedDetectionId(null);
    }
  }, [selectedDetectionId, validItems]);

  return (
    <Card className="overflow-hidden">
      <CardHeader className="border-b">
        <div className="flex items-start justify-between gap-4">
          <div className="flex items-start gap-3">
            <div className="rounded-lg bg-secondary p-2">
              <MapPinned className="size-5 text-primary" />
            </div>
            <div>
              <CardTitle className="text-base">Issues on Map</CardTitle>
              <CardDescription>
                Click a marker, then choose Start or End.
              </CardDescription>
            </div>
          </div>
          {data ? (
            <span className="shrink-0 text-sm text-muted-foreground">
              {validItems.length} mapped
            </span>
          ) : null}
        </div>
      </CardHeader>

      <CardContent className="p-0">
        {isLoading ? (
          <div className="h-[440px] animate-pulse bg-muted" />
        ) : isError ? (
          <div className="flex min-h-72 flex-col items-center justify-center gap-3 px-6 text-center">
            <AlertTriangle className="size-7 text-destructive" />
            <div>
              <p className="font-medium">Unable to load issue locations.</p>
              <p className="mt-1 text-sm text-muted-foreground">
                Check the connection and try again.
              </p>
            </div>
            <Button type="button" variant="outline" onClick={onRetry}>
              <RefreshCw />
              Retry
            </Button>
          </div>
        ) : validItems.length === 0 ? (
          <div className="flex min-h-72 flex-col items-center justify-center px-6 text-center">
            <MapPinned className="size-7 text-muted-foreground" />
            <p className="mt-3 font-medium">No issue locations available.</p>
            <p className="mt-1 text-sm text-muted-foreground">
              Detections without valid coordinates cannot be placed on the map.
            </p>
          </div>
        ) : (
          <>
            <div className="h-[440px] min-w-0 bg-muted">
              <ClientAssignmentIssuesMap
                items={validItems}
                classes={data?.classes ?? []}
                selectedDetectionId={selectedDetectionId}
                startPoint={startPoint}
                endPoint={endPoint}
                onSelectDetection={setSelectedDetectionId}
                onSetStart={onSetStart}
                onSetEnd={onSetEnd}
              />
            </div>

            <div
              className="flex flex-wrap items-center gap-x-4 gap-y-2 border-t px-4 py-3"
              aria-label="Map legend"
            >
              <span className="text-sm font-medium">Map legend</span>
              {(data?.classes ?? []).map((item) => {
                const visual = getDefectVisual(item.class_name);
                const IssueIcon = visual.icon;

                return (
                  <div
                    key={item.class_name}
                    className="flex items-center gap-2 text-sm text-muted-foreground"
                  >
                    <span
                      className="flex size-6 items-center justify-center rounded-full"
                      style={{
                        backgroundColor: `${visual.boundingBoxColor}1f`,
                        color: visual.boundingBoxColor,
                      }}
                    >
                      <IssueIcon className="size-3.5" />
                    </span>
                    <span>{item.display_name}</span>
                  </div>
                );
              })}
            </div>

            <div className="flex items-center justify-between gap-3 border-t px-4 py-3">
              <span className="text-sm text-muted-foreground">
                Showing {loadedCount} of {total}
              </span>
              {hasMore ? (
                <Button
                  type="button"
                  variant="outline"
                  size="sm"
                  onClick={onLoadMore}
                  disabled={isFetchingMore}
                >
                  {isFetchingMore ? 'Loading...' : 'Load more issues'}
                </Button>
              ) : (
                <span className="text-sm text-muted-foreground">
                  All issues are shown
                </span>
              )}
            </div>
          </>
        )}
      </CardContent>
    </Card>
  );
}
