'use client';

import { useState, useMemo } from 'react';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import { useQuery } from '@tanstack/react-query';
import {
  ChevronDown,
  ChevronUp,
  Filter,
  Flame,
  HelpCircle,
  Plus,
  RefreshCw,
  Sparkles,
} from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { BerthOccupancyChip } from '@/components/berths/berth-occupancy-chip';
import { AddBerthToInterestDialog } from '@/components/interests/add-berth-to-interest-dialog';
import { apiFetch } from '@/lib/api/client';
import { cn } from '@/lib/utils';

// ─── Types (mirror the recommender service Recommendation shape) ───────────

type Tier = 'A' | 'B' | 'C' | 'D';

interface HeatBreakdown {
  recency: number;
  furthestStage: number;
  interestCount: number;
  eoiCount: number;
  total: number;
}

export interface Recommendation {
  berthId: string;
  mooringNumber: string;
  area: string | null;
  tier: Tier;
  fitScore: number;
  sizeBufferPct: number | null;
  heat: HeatBreakdown | null;
  reasons: {
    dimensional: string;
    pipeline: string;
    amenities?: string;
    heat?: string;
  };
  lengthFt: number | null;
  widthFt: number | null;
  draftFt: number | null;
  status: string;
  amenities: {
    powerCapacity: number | null;
    voltage: number | null;
    access: string | null;
    mooringType: string | null;
    cleatCapacity: string | null;
  };
}

interface AmenityFilters {
  minPowerCapacityKw?: number;
  requiredVoltage?: number;
  requiredAccess?: string;
  requiredMooringType?: string;
  requiredCleatCapacity?: string;
}

interface BerthRecommenderPanelProps {
  interestId: string;
  /** Display label for the dimensions in the header. */
  desiredLengthFt: number | null;
  desiredWidthFt: number | null;
  desiredDraftFt: number | null;
  /**
   * Unit the rep originally entered the dimensions in. Drives header
   * display so a metric-entered deal doesn't render its dims as ft.
   * Falls back to 'ft' when missing.
   */
  desiredUnit?: 'ft' | 'm' | null;
  /**
   * Number of berths already linked to the interest. When ≥ 1 the panel
   * defaults to collapsed (header-only) so the LinkedBerthsList card above
   * dominates the rep's attention. They can expand to browse more options
   * (multi-berth deals, swap recommendations). Zero / undefined keeps the
   * panel expanded so reps see options immediately.
   */
  linkedBerthCount?: number;
}

const TIER_LABELS: Record<Tier, { label: string; tone: string }> = {
  A: { label: 'Open', tone: 'border-emerald-200 bg-emerald-50 text-emerald-800' },
  B: { label: 'Fall-through', tone: 'border-amber-200 bg-amber-50 text-amber-800' },
  C: { label: 'Active interest', tone: 'border-sky-200 bg-sky-50 text-sky-800' },
  D: { label: 'Late stage', tone: 'border-slate-300 bg-slate-100 text-slate-700' },
};

function statusToPill(status: string): StatusPillStatus {
  switch (status) {
    case 'available':
      return 'active';
    case 'under_offer':
      return 'sent';
    case 'sold':
      return 'completed';
    case 'reserved':
      return 'partial';
    default:
      return 'pending';
  }
}

function formatStatus(status: string): string {
  return status.replace(/_/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase());
}

function formatDimensions(
  length: number | null,
  width: number | null,
  draft: number | null,
): string {
  const parts: string[] = [];
  if (length !== null) parts.push(`${length.toFixed(1)}ft L`);
  if (width !== null) parts.push(`${width.toFixed(1)}ft W`);
  if (draft !== null) parts.push(`${draft.toFixed(1)}ft D`);
  return parts.join(' · ');
}

function formatDesired(
  length: number | null,
  width: number | null,
  draft: number | null,
  unit: 'ft' | 'm' = 'ft',
): string {
  // Storage is canonical-ft (the recommender's SQL ranks against
  // berths.length_ft etc.). For display we convert back to whatever the rep
  // entered. 0.3048 m/ft exactly.
  const toDisplay = (ft: number): string => {
    const v = unit === 'm' ? ft * 0.3048 : ft;
    return v.toFixed(2).replace(/\.?0+$/, '');
  };
  const parts: string[] = [];
  if (length !== null) parts.push(`${toDisplay(length)}${unit} L`);
  if (width !== null) parts.push(`${toDisplay(width)}${unit} W`);
  if (draft !== null) parts.push(`${toDisplay(draft)}${unit} D`);
  return parts.length > 0 ? parts.join(' · ') : 'no dimensions set';
}

interface RecommendationCardProps {
  rec: Recommendation;
  portSlug: string;
  /** Current interest the recommender is running for. Threaded down so
   *  the competing-interest chip can hide rows that point back at this
   *  same interest. */
  interestId: string;
  onAdd: (rec: Recommendation) => void;
}

function RecommendationCard({ rec, portSlug, interestId, onAdd }: RecommendationCardProps) {
  const [expanded, setExpanded] = useState(false);
  const tier = TIER_LABELS[rec.tier];
  const showHeat = rec.heat && rec.heat.total > 0;

  return (
    <div className="rounded-lg border bg-card text-sm">
      <button
        type="button"
        onClick={() => setExpanded((v) => !v)}
        className="flex w-full items-start gap-3 p-3 text-left hover:bg-muted/40"
      >
        <div className="min-w-0 flex-1 space-y-1">
          <div className="flex flex-wrap items-center gap-2">
            {/* Mooring number already carries the area letter prefix
                (canonical `^[A-Z]+\d+$`), so the trailing `· A` was pure
                visual noise — same call as the BerthPicker grouping. */}
            <span className="font-semibold">{rec.mooringNumber}</span>
            <StatusPill status={statusToPill(rec.status)}>{formatStatus(rec.status)}</StatusPill>
            <Popover>
              <PopoverTrigger asChild>
                <button
                  type="button"
                  className={cn(
                    'inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-xs font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-ring',
                    tier.tone,
                  )}
                  aria-label={`Recommender state: ${tier.label}`}
                >
                  {tier.label}
                  <HelpCircle className="size-3 opacity-60" aria-hidden />
                </button>
              </PopoverTrigger>
              <PopoverContent align="start" className="w-80 text-xs leading-relaxed">
                <p className="font-medium text-foreground">Recommender state</p>
                <ul className="mt-2 space-y-1.5 text-muted-foreground">
                  <li>
                    <span className="font-medium text-emerald-700">Open</span>: never had an
                    interest, ready for new prospects.
                  </li>
                  <li>
                    <span className="font-medium text-amber-700">Fall-through</span>: a prior
                    interest didn&apos;t close; warm and worth pitching again.
                  </li>
                  <li>
                    <span className="font-medium text-sky-700">Active interest</span>: another deal
                    is in play. Coordinate before pitching.
                  </li>
                  <li>
                    <span className="font-medium text-slate-700">Late stage</span>: another deal is
                    near-sold; treat as backup only.
                  </li>
                </ul>
              </PopoverContent>
            </Popover>
            {showHeat ? (
              <Popover>
                <PopoverTrigger asChild>
                  <button
                    type="button"
                    className="inline-flex items-center gap-1 rounded-md border border-rose-200 bg-rose-50 px-2 py-0.5 text-xs font-medium text-rose-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                    aria-label={`Heat score: ${Math.round(rec.heat!.total)} out of 100`}
                  >
                    <Flame className="size-3" aria-hidden />
                    Heat {Math.round(rec.heat!.total)}
                    <HelpCircle className="size-3 opacity-60" aria-hidden />
                  </button>
                </PopoverTrigger>
                <PopoverContent align="start" className="w-80 text-xs leading-relaxed">
                  <p className="font-medium text-foreground">
                    Heat score · {Math.round(rec.heat!.total)} / 100
                  </p>
                  <p className="mt-2 text-muted-foreground">
                    How warm this berth is for a re-pitch. Calculated from past interest history:
                    how recent the last fall-through was, how far along it got, how many distinct
                    interests touched it, and how many of those reached EOI. Higher = better target.
                  </p>
                  <ul className="mt-3 space-y-1 text-muted-foreground">
                    <li>
                      <span className="font-medium text-foreground">Recency</span>:{' '}
                      {Math.round(rec.heat!.recency)}
                    </li>
                    <li>
                      <span className="font-medium text-foreground">Furthest stage</span>:{' '}
                      {Math.round(rec.heat!.furthestStage)}
                    </li>
                    <li>
                      <span className="font-medium text-foreground">Interest count</span>:{' '}
                      {Math.round(rec.heat!.interestCount)}
                    </li>
                    <li>
                      <span className="font-medium text-foreground">EOI count</span>:{' '}
                      {Math.round(rec.heat!.eoiCount)}
                    </li>
                  </ul>
                  <p className="mt-3 text-muted-foreground">
                    Admins tune the weights in{' '}
                    <span className="font-medium">Admin → Recommender</span>.
                  </p>
                </PopoverContent>
              </Popover>
            ) : null}
          </div>
          <div className="text-xs text-muted-foreground">
            {formatDimensions(rec.lengthFt, rec.widthFt, rec.draftFt)}
            {rec.sizeBufferPct !== null ? (
              <span>
                {' '}
                · {rec.sizeBufferPct >= 0 ? '+' : ''}
                {rec.sizeBufferPct}% vs desired
              </span>
            ) : null}
            <span className="ml-2 font-medium text-foreground">Fit {rec.fitScore}</span>
          </div>
          {rec.status !== 'available' ? (
            <div className="mt-1">
              <BerthOccupancyChip
                berthId={rec.berthId}
                portSlug={portSlug}
                excludeInterestId={interestId}
                compact
              />
            </div>
          ) : null}
        </div>
        {expanded ? (
          <ChevronUp className="size-4 shrink-0 text-muted-foreground" aria-hidden />
        ) : (
          <ChevronDown className="size-4 shrink-0 text-muted-foreground" aria-hidden />
        )}
      </button>

      {expanded ? (
        <div className="space-y-3 border-t bg-muted/20 p-3">
          <dl className="space-y-1 text-xs">
            <div className="flex gap-2">
              <dt className="w-28 shrink-0 text-muted-foreground">Dimensional</dt>
              <dd>{rec.reasons.dimensional}</dd>
            </div>
            <div className="flex gap-2">
              <dt className="w-28 shrink-0 text-muted-foreground">Pipeline</dt>
              <dd>{rec.reasons.pipeline}</dd>
            </div>
            {rec.reasons.amenities ? (
              <div className="flex gap-2">
                <dt className="w-28 shrink-0 text-muted-foreground">Amenities</dt>
                <dd>{rec.reasons.amenities}</dd>
              </div>
            ) : null}
            {rec.reasons.heat ? (
              <div className="flex gap-2">
                <dt className="w-28 shrink-0 text-muted-foreground">Heat</dt>
                <dd>{rec.reasons.heat}</dd>
              </div>
            ) : null}
          </dl>
          <div className="flex flex-wrap gap-2">
            <Button
              type="button"
              size="sm"
              onClick={(e) => {
                e.stopPropagation();
                onAdd(rec);
              }}
            >
              <Plus className="mr-1.5 size-3.5" aria-hidden />
              Add to interest
            </Button>
            <Button asChild size="sm" variant="outline">
              <Link href={`/${portSlug}/berths/${rec.berthId}`}>View berth</Link>
            </Button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

interface AmenityFilterFormProps {
  filters: AmenityFilters;
  onChange: (next: AmenityFilters) => void;
}

function AmenityFilterForm({ filters, onChange }: AmenityFilterFormProps) {
  const update = <K extends keyof AmenityFilters>(key: K, value: AmenityFilters[K]) => {
    const next = { ...filters };
    if (value === undefined || value === '' || (typeof value === 'number' && Number.isNaN(value))) {
      delete next[key];
    } else {
      next[key] = value;
    }
    onChange(next);
  };
  return (
    <div className="grid grid-cols-1 gap-3 rounded-lg border bg-muted/20 p-3 sm:grid-cols-2 lg:grid-cols-3">
      <div className="space-y-1">
        <Label htmlFor="filter-power" className="text-xs">
          Min power (kW)
        </Label>
        <Input
          id="filter-power"
          type="number"
          min={0}
          step="0.1"
          value={filters.minPowerCapacityKw ?? ''}
          onChange={(e) =>
            update('minPowerCapacityKw', e.target.value ? parseFloat(e.target.value) : undefined)
          }
        />
      </div>
      <div className="space-y-1">
        <Label htmlFor="filter-voltage" className="text-xs">
          Voltage
        </Label>
        <Input
          id="filter-voltage"
          type="number"
          min={0}
          step="1"
          value={filters.requiredVoltage ?? ''}
          onChange={(e) =>
            update('requiredVoltage', e.target.value ? parseInt(e.target.value, 10) : undefined)
          }
        />
      </div>
      <div className="space-y-1">
        <Label htmlFor="filter-mooring" className="text-xs">
          Mooring type
        </Label>
        <Select
          value={filters.requiredMooringType ?? ''}
          onValueChange={(v) => update('requiredMooringType', v || undefined)}
        >
          <SelectTrigger id="filter-mooring">
            <SelectValue placeholder="Any" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="stern_to">Stern-to</SelectItem>
            <SelectItem value="alongside">Alongside</SelectItem>
            <SelectItem value="bow_to">Bow-to</SelectItem>
            <SelectItem value="swing">Swing mooring</SelectItem>
          </SelectContent>
        </Select>
      </div>
      <div className="space-y-1">
        <Label htmlFor="filter-cleat" className="text-xs">
          Cleat capacity
        </Label>
        <Select
          value={filters.requiredCleatCapacity ?? ''}
          onValueChange={(v) => update('requiredCleatCapacity', v || undefined)}
        >
          <SelectTrigger id="filter-cleat">
            <SelectValue placeholder="Any" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="standard">Standard</SelectItem>
            <SelectItem value="heavy">Heavy</SelectItem>
            <SelectItem value="superyacht">Superyacht</SelectItem>
          </SelectContent>
        </Select>
      </div>
      <div className="space-y-1">
        <Label htmlFor="filter-access" className="text-xs">
          Access
        </Label>
        <Select
          value={filters.requiredAccess ?? ''}
          onValueChange={(v) => update('requiredAccess', v || undefined)}
        >
          <SelectTrigger id="filter-access">
            <SelectValue placeholder="Any" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="land">Land access</SelectItem>
            <SelectItem value="dinghy">Dinghy only</SelectItem>
            <SelectItem value="walk_on">Walk-on</SelectItem>
          </SelectContent>
        </Select>
      </div>
    </div>
  );
}

// destructure includes `desiredUnit` so the header formatter pivots on the
// rep's entered unit. Falls back to 'ft' (the legacy default) when missing.
export function BerthRecommenderPanel({
  interestId,
  desiredLengthFt,
  desiredWidthFt,
  desiredDraftFt,
  desiredUnit,
  linkedBerthCount,
}: BerthRecommenderPanelProps) {
  const params = useParams<{ portSlug: string }>();
  const portSlug = params?.portSlug ?? '';

  const [filtersOpen, setFiltersOpen] = useState(false);
  const [amenityFilters, setAmenityFilters] = useState<AmenityFilters>({});
  const [showAll, setShowAll] = useState(false);
  const [pendingBerth, setPendingBerth] = useState<Recommendation | null>(null);
  // Area-letter filter - chips above the list let reps narrow to a
  // single pier (e.g. "show me only A-row matches"). Client-side over
  // the already-fetched result set; no service change required.
  const [selectedAreas, setSelectedAreas] = useState<string[]>([]);
  // Collapse state - defaults to collapsed when the deal already has at
  // least one linked berth (recommender becomes a "browse more options"
  // tool rather than the primary surface). Reps can manually expand any
  // time. Header click toggles.
  const [collapsed, setCollapsed] = useState<boolean>((linkedBerthCount ?? 0) > 0);

  const hasDimensions = desiredLengthFt !== null;

  const queryKey = useMemo(
    () => ['berth-recommendations', interestId, amenityFilters, showAll] as const,
    [interestId, amenityFilters, showAll],
  );

  const { data, isFetching, refetch } = useQuery({
    queryKey,
    // Skip the network call when collapsed - no point fetching options
    // the rep won't see. Re-fires automatically on expand.
    enabled: hasDimensions && !collapsed,
    queryFn: () =>
      apiFetch<{ data: Recommendation[] }>(`/api/v1/interests/${interestId}/recommend-berths`, {
        method: 'POST',
        body: {
          // `showAll` opens the floodgates: bumps `topN` AND raises the
          // oversize-cap so berths well beyond the strict feasibility window
          // surface. Without that second bump the user could end up staring
          // at "no berths match" when the test data only had oversized rows
          // - exactly the case in our seeded demo port.
          ...(showAll ? { topN: 999, maxOversizePct: 1000 } : {}),
          ...(Object.keys(amenityFilters).length > 0 ? { amenityFilters } : {}),
        },
      }).then((r) => r.data),
    staleTime: 60_000,
  });

  const allRecommendations = data ?? [];

  // Build the set of dock-letter chips from whatever came back, then
  // filter the visible recommendations by the active selection. Empty
  // selection = show everything (default).
  const areaChips = useMemo(() => {
    const set = new Set<string>();
    for (const r of allRecommendations) {
      const m = r.mooringNumber.match(/^([A-Z]+)/);
      if (m?.[1]) set.add(m[1]);
    }
    return Array.from(set).sort();
  }, [allRecommendations]);

  const recommendations =
    selectedAreas.length === 0
      ? allRecommendations
      : allRecommendations.filter((r) => {
          const m = r.mooringNumber.match(/^([A-Z]+)/);
          return m?.[1] ? selectedAreas.includes(m[1]) : false;
        });

  return (
    <Card>
      <CardHeader className="gap-3">
        <div className="flex flex-wrap items-start justify-between gap-2">
          <div className="min-w-0 space-y-1">
            <CardTitle className="flex items-center gap-2 text-base">
              <Sparkles className="size-4 text-brand-600" aria-hidden />
              Recommendations for{' '}
              {formatDesired(
                desiredLengthFt,
                desiredWidthFt,
                desiredDraftFt,
                desiredUnit === 'm' ? 'm' : 'ft',
              )}
            </CardTitle>
            {!hasDimensions ? (
              <p className="text-xs text-muted-foreground">
                Set desired dimensions to see recommendations.
              </p>
            ) : null}
          </div>
          <div className="flex flex-wrap items-center gap-2">
            {!collapsed ? (
              <>
                <Button
                  type="button"
                  size="sm"
                  variant="outline"
                  onClick={() => setFiltersOpen((v) => !v)}
                  disabled={!hasDimensions}
                  aria-expanded={filtersOpen}
                  aria-controls="recommender-filters-body"
                >
                  <Filter className="mr-1.5 size-3.5" aria-hidden />
                  {filtersOpen ? 'Hide filters' : 'Add filters'}
                </Button>
                <Button
                  type="button"
                  size="sm"
                  variant="outline"
                  onClick={() => refetch()}
                  disabled={!hasDimensions || isFetching}
                >
                  <RefreshCw className={cn('mr-1.5 size-3.5', isFetching && 'animate-spin')} />
                  Refresh
                </Button>
              </>
            ) : null}
            <Button
              type="button"
              size="sm"
              variant="ghost"
              onClick={() => setCollapsed((v) => !v)}
              aria-expanded={!collapsed}
              aria-controls={`recommender-body-${interestId}`}
            >
              {collapsed ? (
                <>
                  <ChevronDown className="mr-1.5 size-3.5" aria-hidden />
                  Show recommendations
                </>
              ) : (
                <>
                  <ChevronUp className="mr-1.5 size-3.5" aria-hidden />
                  Hide
                </>
              )}
            </Button>
          </div>
        </div>
        {!collapsed && filtersOpen && hasDimensions ? (
          <div id="recommender-filters-body">
            <AmenityFilterForm filters={amenityFilters} onChange={setAmenityFilters} />
          </div>
        ) : null}
        {!collapsed && hasDimensions && areaChips.length > 1 ? (
          <div className="flex flex-wrap items-center gap-1.5 pt-1">
            <span className="text-xs font-medium text-muted-foreground">Area:</span>
            {areaChips.map((letter) => {
              const active = selectedAreas.includes(letter);
              return (
                <button
                  key={letter}
                  type="button"
                  onClick={() =>
                    setSelectedAreas((prev) =>
                      prev.includes(letter) ? prev.filter((l) => l !== letter) : [...prev, letter],
                    )
                  }
                  className={cn(
                    'rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors',
                    active
                      ? 'border-primary bg-primary text-primary-foreground'
                      : 'border-input bg-background text-foreground hover:bg-muted',
                  )}
                  aria-pressed={active}
                >
                  {letter}
                </button>
              );
            })}
            {selectedAreas.length > 0 ? (
              <button
                type="button"
                onClick={() => setSelectedAreas([])}
                className="text-xs text-muted-foreground underline ml-1"
              >
                Clear
              </button>
            ) : null}
          </div>
        ) : null}
      </CardHeader>
      {collapsed ? null : (
        <CardContent className="space-y-3" id={`recommender-body-${interestId}`}>
          {!hasDimensions ? (
            <p className="text-sm text-muted-foreground">
              Once length, width, and draft are set on this interest, the recommender will surface
              berths that fit. Edit the desired dimensions on the{' '}
              <Link href="?tab=overview" className="text-primary underline">
                Overview tab
              </Link>
              .
            </p>
          ) : isFetching && recommendations.length === 0 ? (
            <div className="space-y-2">
              {[0, 1, 2].map((i) => (
                <div key={i} className="h-16 animate-pulse rounded-lg bg-muted" />
              ))}
            </div>
          ) : recommendations.length === 0 ? (
            <div className="py-6 text-center text-sm text-muted-foreground space-y-2">
              <p>
                {showAll
                  ? 'No berths in the port match these dimensions and filters.'
                  : 'No berths fit inside the strict oversize tolerance.'}
              </p>
              {!showAll && (
                <Button type="button" size="sm" variant="outline" onClick={() => setShowAll(true)}>
                  Show oversized matches too
                </Button>
              )}
            </div>
          ) : (
            <div className="space-y-2">
              {recommendations.map((rec) => (
                <RecommendationCard
                  key={rec.berthId}
                  rec={rec}
                  portSlug={portSlug}
                  interestId={interestId}
                  onAdd={setPendingBerth}
                />
              ))}
            </div>
          )}
          {hasDimensions && recommendations.length > 0 ? (
            <div className="flex justify-center pt-1">
              <Button type="button" size="sm" variant="ghost" onClick={() => setShowAll((v) => !v)}>
                {showAll ? 'Show top in-tolerance only' : 'Show oversized matches too'}
              </Button>
            </div>
          ) : null}
        </CardContent>
      )}

      {pendingBerth ? (
        <AddBerthToInterestDialog
          interestId={interestId}
          berth={{
            berthId: pendingBerth.berthId,
            mooringNumber: pendingBerth.mooringNumber,
          }}
          open={pendingBerth !== null}
          onOpenChange={(open) => {
            if (!open) setPendingBerth(null);
          }}
        />
      ) : null}
    </Card>
  );
}
