'use client';

import { useParams } from 'next/navigation';
import { useMemo } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { DndContext, closestCenter, type DragEndEvent } from '@dnd-kit/core';

import { PipelineColumn } from '@/components/interests/pipeline-column';
import { apiFetch } from '@/lib/api/client';
import { PIPELINE_STAGES, STAGE_LABELS } from '@/lib/constants';
import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation';

interface InterestRow {
  id: string;
  clientName: string | null;
  berthMooringNumber: string | null;
  berthMoorings?: string[];
  leadCategory: string | null;
  pipelineStage: string;
  updatedAt: string;
}

interface BoardResponse {
  data: InterestRow[];
  truncated: boolean;
  total: number;
}

interface PipelineBoardProps {
  /** Filter values from the parent's FilterBar - passed through to the
   *  /api/v1/interests/board endpoint. Subset of listInterests filters
   *  (no pipelineStage, no includeArchived). Optional; board works
   *  fine without filters. */
  filters?: Record<string, unknown>;
}

export function PipelineBoard({ filters }: PipelineBoardProps = {}) {
  const params = useParams<{ portSlug: string }>();
  const portSlug = params?.portSlug ?? '';
  const queryClient = useQueryClient();

  // Build the board endpoint URL with the supported filter subset.
  // pipelineStage + includeArchived are intentionally not threaded
  // through - see boardFiltersSchema on the backend. Stable JSON-string
  // form is reused as the queryKey so React Query caches per filter combo.
  const queryString = useMemo(() => {
    if (!filters) return '';
    const params = new URLSearchParams();
    const pick = (k: string) => {
      const v = filters[k];
      if (v === null || v === undefined || v === '' || v === false) return;
      if (Array.isArray(v)) {
        if (v.length === 0) return;
        params.set(k, v.join(','));
      } else {
        params.set(k, String(v));
      }
    };
    pick('search');
    pick('leadCategory');
    pick('source');
    pick('eoiStatus');
    pick('tagIds');
    const s = params.toString();
    return s ? `?${s}` : '';
  }, [filters]);

  const boardQueryKey = ['interests-board', portSlug, queryString] as const;
  // Dedicated board endpoint - bypasses the paginated list's max(100)
  // cap, projects only the 5 fields PipelineCard renders, and hard-caps
  // at 5000 server-side. If `truncated: true`, surface a banner so the
  // rep knows the board isn't showing every active deal.
  const {
    data: allData,
    isLoading,
    error,
  } = useQuery<BoardResponse>({
    queryKey: boardQueryKey,
    queryFn: () => apiFetch(`/api/v1/interests/board${queryString}`),
  });

  // Invalidate the entire ['interests-board', portSlug, *] family so
  // realtime events refresh whatever filter combo is currently active.
  // Using the prefix keeps stale per-filter caches from lingering after
  // the underlying data changes elsewhere in the app.
  useRealtimeInvalidation({
    'interest:created': [['interests-board', portSlug]],
    'interest:updated': [['interests-board', portSlug]],
    'interest:stageChanged': [['interests-board', portSlug]],
    'interest:archived': [['interests-board', portSlug]],
  });

  const interests = useMemo(() => allData?.data ?? [], [allData]);

  const grouped = useMemo(() => {
    const map: Record<string, InterestRow[]> = {};
    for (const stage of PIPELINE_STAGES) {
      map[stage] = [];
    }
    for (const interest of interests) {
      if (map[interest.pipelineStage]) {
        map[interest.pipelineStage]!.push(interest);
      }
    }
    return map;
  }, [interests]);

  async function handleDragEnd(event: DragEndEvent) {
    const { active, over } = event;
    if (!over || active.id === over.id) return;

    // over.id is a stage when dropped on a column, or an item id when dropped on a card
    let newStage = over.id as string;

    // If dropped on a card (not a stage), find which stage that card belongs to
    if (!PIPELINE_STAGES.includes(newStage as (typeof PIPELINE_STAGES)[number])) {
      const targetInterest = interests.find((i) => i.id === newStage);
      if (!targetInterest) return;
      newStage = targetInterest.pipelineStage;
    }

    const interestId = active.id as string;
    const currentInterest = interests.find((i) => i.id === interestId);
    if (!currentInterest || currentInterest.pipelineStage === newStage) return;

    // Optimistic update
    queryClient.setQueryData<{ data: InterestRow[] }>(['interests-board', portSlug], (old) => {
      if (!old) return old;
      return {
        ...old,
        data: old.data.map((i) => (i.id === interestId ? { ...i, pipelineStage: newStage } : i)),
      };
    });

    try {
      await apiFetch(`/api/v1/interests/${interestId}/stage`, {
        method: 'PATCH',
        body: { pipelineStage: newStage },
      });
      queryClient.invalidateQueries({ queryKey: ['interests'] });
    } catch {
      // Revert optimistic update
      queryClient.invalidateQueries({ queryKey: ['interests-board', portSlug] });
    }
  }

  if (isLoading) {
    return <div className="flex gap-3 overflow-x-auto pb-4 animate-pulse h-64" />;
  }

  // Surface fetch failures instead of silently rendering nine "Empty"
  // columns, which is indistinguishable from "no interests yet" and was
  // exactly the bug that hid this view's silent failure for so long.
  if (error) {
    return (
      <div className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-6 text-sm text-destructive">
        Couldn&apos;t load the pipeline board.{' '}
        <button
          className="underline underline-offset-2"
          onClick={() => queryClient.invalidateQueries({ queryKey: boardQueryKey })}
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      {allData?.truncated ? (
        <div className="mb-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900">
          Showing the {allData.total.toLocaleString()} most-recently-updated interests. Older active
          deals aren&apos;t on the board - archive completed work to keep the kanban readable.
        </div>
      ) : null}
      <div className="flex gap-3 overflow-x-auto pb-4">
        {PIPELINE_STAGES.map((stage) => (
          <PipelineColumn
            key={stage}
            stage={stage}
            label={STAGE_LABELS[stage]}
            items={grouped[stage] ?? []}
          />
        ))}
      </div>
    </DndContext>
  );
}
