'use client';

import { useCallback, useMemo, useState } from 'react';
import Link from 'next/link';
import type { Route } from 'next';
import { useSearchParams } from 'next/navigation';
import { useQuery } from '@tanstack/react-query';
import {
  Area,
  AreaChart,
  Bar,
  BarChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
  Cell,
} from 'recharts';
import { Anchor, FileWarning, AlertTriangle } from 'lucide-react';

import { PageHeader } from '@/components/shared/page-header';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { DateRangePicker } from '@/components/dashboard/date-range-picker';
import {
  FilterBar,
  type FilterDefinition,
  type FilterValues,
} from '@/components/shared/filter-bar';
import { ReportExportButton } from '@/components/reports/shared/report-export-button';
import { ReportTemplatesButton } from '@/components/reports/shared/report-templates-button';
import { formatMoneyCompact as formatMoney } from '@/lib/reports/format-currency';
import { rangeToBounds, type DateRange } from '@/lib/analytics/range';
import { apiFetch } from '@/lib/api/client';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/ui-store';
import type { ReportPayload } from '@/lib/reports/types';
import { ReportEmptyState } from '@/components/reports/shared/report-empty-state';

import { OperationalHeatmap } from './operational-heatmap';
import { OperationalSigningBoxPlot } from './operational-signing-box-plot';

// ─── Types mirroring the service ─────────────────────────────────────────────

interface OperationalKpis {
  totalBerths: number;
  soldPct: number;
  soldPctDelta: number | null;
  underOfferPct: number;
  underOfferPctDelta: number | null;
  tenanciesModuleEnabled: boolean;
  activeTenancies: number | null;
  avgTenancyLengthYears: number | null;
  signingTurnaround: Array<{
    documentType: string;
    medianDays: number | null;
    sampleSize: number;
  }>;
  berthsInConflict: number;
}

interface UtilisationCell {
  area: string;
  month: string;
  occupancyPct: number;
}

interface StatusMixPoint {
  month: string;
  available: number;
  underOffer: number;
  sold: number;
}

interface TenancyChurnPoint {
  bucket: string;
  newActive: number;
  ended: number;
  netDelta: number;
}

interface TenureBucket {
  label: string;
  count: number;
}

interface SigningBoxPlot {
  documentType: string;
  min: number;
  q1: number;
  median: number;
  q3: number;
  max: number;
  sampleSize: number;
}

interface AreaOccupancyRow {
  area: string;
  available: number;
  underOffer: number;
  sold: number;
  total: number;
}

interface DocsPipelineRow {
  documentType: string;
  draft: number;
  sent: number;
  partiallySigned: number;
  completed: number;
  declined: number;
  voided: number;
}

interface EndingSoonRow {
  id: string;
  clientName: string;
  primaryBerth: string | null;
  tenureType: string;
  endDate: string;
  daysUntilEnd: number;
}

interface VacantBerthRow {
  id: string;
  mooring: string;
  area: string | null;
  dimensions: string;
  price: number | null;
  currency: string;
  daysAvailable: number | null;
}

interface StuckSigningRow {
  id: string;
  documentType: string;
  title: string;
  clientName: string | null;
  sentAt: string;
  daysOutstanding: number;
}

interface HighestValueVacantRow {
  id: string;
  mooring: string;
  area: string | null;
  dimensions: string;
  price: number;
  currency: string;
  daysAvailable: number | null;
}

interface OperationalReportPayload {
  data: {
    kpis: OperationalKpis;
    utilisationHeatmap: UtilisationCell[];
    statusMix: StatusMixPoint[];
    tenancyChurn: { granularity: 'month' | 'quarter'; points: TenancyChurnPoint[] };
    tenureDistribution: TenureBucket[];
    signingBoxPlot: SigningBoxPlot[];
    occupancyByArea: AreaOccupancyRow[];
    docsInPipeline: DocsPipelineRow[];
    endingSoon: EndingSoonRow[];
    vacantBerths: VacantBerthRow[];
    stuckSigning: StuckSigningRow[];
    highestValueVacant: HighestValueVacantRow[];
    range: { from: string; to: string };
    hasData: boolean;
    areaOptions: string[];
  };
}

interface OperationalTemplateConfig extends Record<string, unknown> {
  kind: 'operational';
  range: DateRange;
  statusMixMode: 'absolute' | 'proportional';
  filters?: FilterValues;
}

export function OperationalReportClient({ portSlug }: { portSlug: string }) {
  const searchParams = useSearchParams();
  const initialTemplateId = searchParams?.get('templateId') ?? null;

  const [range, setRange] = useState<DateRange>('30d');
  const [statusMixMode, setStatusMixMode] = useState<'absolute' | 'proportional'>('proportional');
  const [activeTemplateId, setActiveTemplateId] = useState<string | null>(initialTemplateId);
  const [filterValues, setFilterValues] = useState<FilterValues>({});

  // User-driven setters clear the active-template badge; template
  // apply uses the raw setters so it doesn't immediately clear its
  // own badge.
  const handleRangeChange = useCallback((next: DateRange) => {
    setRange(next);
    setActiveTemplateId(null);
  }, []);

  const handleStatusMixChange = useCallback((next: 'absolute' | 'proportional') => {
    setStatusMixMode(next);
    setActiveTemplateId(null);
  }, []);

  const handleFilterChange = useCallback((key: string, value: unknown) => {
    setFilterValues((prev) => ({ ...prev, [key]: value }));
    setActiveTemplateId(null);
  }, []);

  const handleFiltersClear = useCallback(() => {
    setFilterValues({});
    setActiveTemplateId(null);
  }, []);

  const currentConfig: OperationalTemplateConfig = useMemo(
    () => ({ kind: 'operational', range, statusMixMode, filters: filterValues }),
    [range, statusMixMode, filterValues],
  );

  const handleApplyTemplate = useCallback((config: OperationalTemplateConfig) => {
    if (config.range) setRange(config.range);
    if (config.statusMixMode) setStatusMixMode(config.statusMixMode);
    setFilterValues(config.filters ?? {});
  }, []);

  const bounds = useMemo(() => rangeToBounds(range), [range]);

  const filterQs = useMemo(() => {
    const areas = filterValues.area;
    return Array.isArray(areas) && areas.length > 0
      ? `&area=${encodeURIComponent(areas.join(','))}`
      : '';
  }, [filterValues]);

  const query = useQuery<OperationalReportPayload>({
    queryKey: [
      'reports',
      'operational',
      bounds.from.toISOString(),
      bounds.to.toISOString(),
      filterQs,
    ],
    queryFn: () =>
      apiFetch<OperationalReportPayload>(
        `/api/v1/reports/operational?from=${encodeURIComponent(bounds.from.toISOString())}&to=${encodeURIComponent(bounds.to.toISOString())}${filterQs}`,
      ),
    staleTime: 30_000,
  });

  const data = query.data?.data;
  const tenanciesOn = data?.kpis.tenanciesModuleEnabled ?? false;

  const areaOptions = query.data?.data.areaOptions;
  const filterDefs = useMemo<FilterDefinition[]>(() => {
    if (!areaOptions || areaOptions.length === 0) return [];
    return [
      {
        key: 'area',
        label: 'Berth area',
        type: 'multi-select',
        options: areaOptions.map((a) => ({ value: a, label: a })),
      },
    ];
  }, [areaOptions]);

  function buildExportPayload(): ReportPayload {
    if (!data) throw new Error('Report still loading');
    return {
      title: 'Operational',
      description:
        'Berth utilisation, tenancy lifecycle, signing turnaround, and operational bottlenecks.',
      filenameSlug: 'operational',
      range: bounds,
      kpis: [
        { label: 'Total berths', value: data.kpis.totalBerths },
        { label: 'Sold %', value: `${data.kpis.soldPct.toFixed(1)}%` },
        { label: 'Under offer %', value: `${data.kpis.underOfferPct.toFixed(1)}%` },
        {
          label: 'Active tenancies',
          value: data.kpis.activeTenancies ?? '—',
          hint: tenanciesOn ? undefined : 'Tenancies module disabled',
        },
        {
          label: 'Avg tenancy length',
          value:
            data.kpis.avgTenancyLengthYears !== null
              ? `${data.kpis.avgTenancyLengthYears.toFixed(1)} years`
              : '—',
        },
        { label: 'Berths in conflict', value: data.kpis.berthsInConflict },
      ],
      sections: [
        {
          title: 'Occupancy by area',
          columns: [
            { key: 'area', label: 'Area' },
            { key: 'available', label: 'Available', align: 'right' },
            { key: 'underOffer', label: 'Under offer', align: 'right' },
            { key: 'sold', label: 'Sold', align: 'right' },
            { key: 'total', label: 'Total', align: 'right' },
          ],
          rows: data.occupancyByArea.map((r) => ({ ...r })),
        },
        {
          title: 'Tenancies ending soon (next 6 months)',
          columns: [
            { key: 'clientName', label: 'Client' },
            { key: 'primaryBerth', label: 'Berth' },
            { key: 'tenureType', label: 'Tenure type' },
            { key: 'endDate', label: 'End date', format: (v) => String(v).slice(0, 10) },
            { key: 'daysUntilEnd', label: 'Days until end', align: 'right' },
          ],
          rows: data.endingSoon.map((r) => ({ ...r })),
        },
        {
          title: 'Vacant berths (>60 days)',
          columns: [
            { key: 'mooring', label: 'Mooring' },
            { key: 'area', label: 'Area' },
            { key: 'dimensions', label: 'Dimensions' },
            { key: 'price', label: 'Price', align: 'right' },
            { key: 'daysAvailable', label: 'Days available', align: 'right' },
          ],
          // Pre-format `price` per row so each cell uses its row's
          // currency. The shared `format` callback only sees the value,
          // not the row, so we resolve the currency at row-build time
          // and emit a string. Null prices show "—".
          rows: data.vacantBerths.map((r) => ({
            ...r,
            price: r.price !== null ? formatMoney(r.price, r.currency) : '—',
          })),
        },
        {
          title: 'Stuck signing',
          columns: [
            { key: 'documentType', label: 'Document type' },
            { key: 'title', label: 'Title' },
            { key: 'clientName', label: 'Client' },
            { key: 'sentAt', label: 'Sent at', format: (v) => String(v).slice(0, 10) },
            { key: 'daysOutstanding', label: 'Days outstanding', align: 'right' },
          ],
          rows: data.stuckSigning.map((r) => ({ ...r })),
        },
        {
          title: 'Highest-value vacant berths',
          columns: [
            { key: 'mooring', label: 'Mooring' },
            { key: 'area', label: 'Area' },
            { key: 'dimensions', label: 'Dimensions' },
            { key: 'price', label: 'Price', align: 'right' },
            { key: 'daysAvailable', label: 'Days available', align: 'right' },
          ],
          rows: data.highestValueVacant.map((r) => ({
            ...r,
            price: formatMoney(r.price, r.currency),
          })),
        },
      ],
    };
  }

  if (!query.isLoading && data && !data.hasData) {
    return (
      <div className="space-y-6">
        <PageHeader
          eyebrow="Reports"
          title="Operational"
          description="Berth utilisation, tenancy lifecycle, signing turnaround, operational bottlenecks."
        />
        <ReportEmptyState
          icon={Anchor}
          title="No berths yet"
          body="Add berths to see utilisation, occupancy, and signing turnaround."
          actionLabel="Add berths"
          actionHref={`/${portSlug}/berths` as Route}
        />
      </div>
    );
  }

  return (
    <div className="space-y-6">
      <PageHeader
        eyebrow="Reports"
        title="Operational"
        description="Berth utilisation, tenancy lifecycle, signing turnaround, operational bottlenecks."
        actions={
          <div className="flex items-center gap-2">
            {filterDefs.length > 0 ? (
              <FilterBar
                filters={filterDefs}
                values={filterValues}
                onChange={handleFilterChange}
                onClear={handleFiltersClear}
              />
            ) : null}
            <DateRangePicker value={range} onChange={handleRangeChange} />
            <ReportTemplatesButton<OperationalTemplateConfig>
              kind="operational"
              currentConfig={currentConfig}
              onApply={handleApplyTemplate}
              activeTemplateId={activeTemplateId}
              onActiveTemplateChange={setActiveTemplateId}
              initialTemplateId={initialTemplateId}
            />
            <ReportExportButton buildPayload={buildExportPayload} disabled={!data} />
          </div>
        }
      />

      {Array.isArray(filterValues.area) && filterValues.area.length > 0 ? (
        <p className="text-xs text-muted-foreground">
          Berth surfaces (KPIs, occupancy, vacant lists) scoped to:{' '}
          <span className="font-medium text-foreground">
            {(filterValues.area as string[]).join(', ')}
          </span>
          . Trend and tenancy panels show the full port.
        </p>
      ) : null}

      {/* KPI strip */}
      <section className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
        {query.isLoading || !data ? (
          Array.from({ length: 7 }).map((_, i) => <KpiSkeleton key={i} />)
        ) : (
          <>
            <KpiCard label="Total berths" value={String(data.kpis.totalBerths)} />
            <KpiCard
              label="Sold %"
              value={`${data.kpis.soldPct.toFixed(1)}%`}
              delta={data.kpis.soldPctDelta}
            />
            <KpiCard
              label="Under offer %"
              value={`${data.kpis.underOfferPct.toFixed(1)}%`}
              delta={data.kpis.underOfferPctDelta}
            />
            <KpiCard
              label="Active tenancies"
              value={tenanciesOn ? String(data.kpis.activeTenancies ?? 0) : '—'}
              hint={tenanciesOn ? undefined : 'Tenancies module disabled'}
            />
            <KpiCard
              label="Avg tenancy length"
              value={
                tenanciesOn && data.kpis.avgTenancyLengthYears !== null
                  ? `${data.kpis.avgTenancyLengthYears.toFixed(1)}y`
                  : '—'
              }
              hint={
                tenanciesOn
                  ? data.kpis.avgTenancyLengthYears !== null
                    ? undefined
                    : 'Need ≥3 ended tenancies'
                  : 'Tenancies module disabled'
              }
            />
            <KpiCard
              label="Signing turnaround"
              value={
                data.kpis.signingTurnaround.length === 0
                  ? '—'
                  : data.kpis.signingTurnaround
                      .map(
                        (t) =>
                          `${formatType(t.documentType)} ${
                            t.medianDays !== null ? `${t.medianDays.toFixed(1)}d` : '—'
                          }`,
                      )
                      .join(' · ')
              }
              hint={
                data.kpis.signingTurnaround.length === 0
                  ? 'No completed documents yet'
                  : `${data.kpis.signingTurnaround.reduce((acc, t) => acc + t.sampleSize, 0)} documents`
              }
              valueSmall
            />
            {data.kpis.berthsInConflict > 0 ? (
              <KpiCard
                label="Berths in conflict"
                value={String(data.kpis.berthsInConflict)}
                tone="danger"
                hint="Multiple active interests on the same berth"
              />
            ) : null}
          </>
        )}
      </section>

      {/* Module-OFF banner for tenancies-disabled ports */}
      {!query.isLoading && data && !tenanciesOn ? (
        <div className="rounded-md border border-dashed border-amber-300 bg-amber-50/50 p-4 flex items-start gap-3">
          <AlertTriangle className="h-5 w-5 text-amber-600 shrink-0 mt-0.5" aria-hidden />
          <div className="text-sm text-amber-900 flex-1">
            <p className="font-medium">Tenancies module is disabled for this port.</p>
            <p className="text-amber-800/80 mt-1">
              Tenancy-related KPIs, the tenancy churn waterfall, tenure distribution, and the
              ending-soon table are hidden. Enable in{' '}
              <Link
                href={`/${portSlug}/admin/settings` as Route}
                className="underline font-medium hover:text-amber-950"
              >
                System Settings
              </Link>
              .
            </p>
          </div>
        </div>
      ) : null}

      {/* CHART 1 - Utilisation heatmap */}
      <Card>
        <CardHeader>
          <CardTitle className="text-base">Berth utilisation timeline</CardTitle>
          <p className="text-xs text-muted-foreground">
            Area × month occupancy (sold + under-offer share of berths). Trailing 24 months.
          </p>
        </CardHeader>
        <CardContent>
          {query.isLoading || !data ? (
            <Skeleton className="h-[320px] w-full" />
          ) : (
            <OperationalHeatmap cells={data.utilisationHeatmap} />
          )}
        </CardContent>
      </Card>

      {/* CHART 2 - Status mix over time */}
      <Card>
        <CardHeader>
          <div className="flex items-start justify-between gap-4 flex-wrap">
            <div>
              <CardTitle className="text-base">Status mix over time</CardTitle>
              <p className="text-xs text-muted-foreground">
                Port-wide available / under-offer / sold by month.
              </p>
            </div>
            <div className="inline-flex rounded-md border border-border p-0.5 text-xs">
              <button
                type="button"
                onClick={() => handleStatusMixChange('proportional')}
                className={cn(
                  'px-2.5 py-1 rounded transition-colors',
                  statusMixMode === 'proportional'
                    ? 'bg-brand-500 text-white'
                    : 'text-muted-foreground hover:text-foreground',
                )}
              >
                Proportional
              </button>
              <button
                type="button"
                onClick={() => handleStatusMixChange('absolute')}
                className={cn(
                  'px-2.5 py-1 rounded transition-colors',
                  statusMixMode === 'absolute'
                    ? 'bg-brand-500 text-white'
                    : 'text-muted-foreground hover:text-foreground',
                )}
              >
                Absolute
              </button>
            </div>
          </div>
        </CardHeader>
        <CardContent>
          {query.isLoading || !data ? (
            <Skeleton className="h-[260px] w-full" />
          ) : (
            <StatusMixChart data={data.statusMix} mode={statusMixMode} />
          )}
        </CardContent>
      </Card>

      {/* CHARTS 3 + 4 - Tenancy charts (gated on module ON) */}
      {tenanciesOn && data ? (
        <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
          <Card>
            <CardHeader>
              <CardTitle className="text-base">Tenancy churn</CardTitle>
              <p className="text-xs text-muted-foreground">
                New active vs ended per {data.tenancyChurn.granularity}.
              </p>
            </CardHeader>
            <CardContent>
              <TenancyChurnChart points={data.tenancyChurn.points} />
            </CardContent>
          </Card>
          <Card>
            <CardHeader>
              <CardTitle className="text-base">Tenure distribution</CardTitle>
              <p className="text-xs text-muted-foreground">
                Ended tenancies by length. Marina-tuned buckets.
              </p>
            </CardHeader>
            <CardContent>
              <TenureHistogram rows={data.tenureDistribution} />
            </CardContent>
          </Card>
        </div>
      ) : null}

      {/* CHART 5 - Signing box plot */}
      <Card>
        <CardHeader>
          <CardTitle className="text-base">Signing turnaround</CardTitle>
          <p className="text-xs text-muted-foreground">
            Distribution of days from sent → completed per document type. Box = median + Q1/Q3;
            whiskers = min/max.
          </p>
        </CardHeader>
        <CardContent>
          {query.isLoading || !data ? (
            <Skeleton className="h-[260px] w-full" />
          ) : (
            <OperationalSigningBoxPlot rows={data.signingBoxPlot} />
          )}
        </CardContent>
      </Card>

      {/* CHART 6 + 7 - Occupancy by area + Documents in pipeline */}
      {data ? (
        <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
          <Card>
            <CardHeader>
              <CardTitle className="text-base">Occupancy by area</CardTitle>
              <p className="text-xs text-muted-foreground">Current status mix per port area.</p>
            </CardHeader>
            <CardContent>
              <OccupancyByAreaChart rows={data.occupancyByArea} />
            </CardContent>
          </Card>
          <Card>
            <CardHeader>
              <CardTitle className="text-base">Documents in pipeline</CardTitle>
              <p className="text-xs text-muted-foreground">Current state per document type.</p>
            </CardHeader>
            <CardContent>
              <DocsPipelineChart rows={data.docsInPipeline} />
            </CardContent>
          </Card>
        </div>
      ) : null}

      {/* TABLES */}
      {data ? (
        <div className="space-y-6">
          {tenanciesOn ? <EndingSoonTable rows={data.endingSoon} portSlug={portSlug} /> : null}
          <VacantBerthsTable rows={data.vacantBerths} portSlug={portSlug} />
          <StuckSigningTable rows={data.stuckSigning} portSlug={portSlug} />
          <HighestValueVacantTable rows={data.highestValueVacant} portSlug={portSlug} />
        </div>
      ) : null}
    </div>
  );
}

// ─── KPI primitives ──────────────────────────────────────────────────────────

function KpiCard({
  label,
  value,
  hint,
  delta,
  tone = 'neutral',
  valueSmall,
}: {
  label: string;
  value: string;
  hint?: string;
  delta?: number | null;
  tone?: 'neutral' | 'danger';
  valueSmall?: boolean;
}) {
  return (
    <Card
      className={cn('h-full p-4 space-y-1.5', tone === 'danger' && 'border-rose-300 bg-rose-50/40')}
    >
      <p className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
        {label}
      </p>
      <p
        className={cn(
          'font-semibold tracking-tight text-foreground tabular-nums',
          valueSmall ? 'text-sm' : 'text-2xl',
          tone === 'danger' && 'text-rose-700',
        )}
      >
        {value}
      </p>
      {delta !== undefined && delta !== null ? (
        <p
          className={cn(
            'text-[11px] tabular-nums',
            delta > 0 ? 'text-emerald-700' : delta < 0 ? 'text-rose-700' : 'text-muted-foreground',
          )}
        >
          {delta > 0 ? '+' : ''}
          {delta.toFixed(1)}pp vs period start
        </p>
      ) : null}
      {hint ? (
        <p className="text-[11px] text-muted-foreground leading-snug line-clamp-2">{hint}</p>
      ) : null}
    </Card>
  );
}

function KpiSkeleton() {
  return (
    <Card className="h-full p-4 space-y-2">
      <Skeleton className="h-3 w-20" />
      <Skeleton className="h-7 w-16" />
      <Skeleton className="h-3 w-32" />
    </Card>
  );
}

// ─── Inline chart components ─────────────────────────────────────────────────

function StatusMixChart({
  data,
  mode,
}: {
  data: StatusMixPoint[];
  mode: 'absolute' | 'proportional';
}) {
  if (data.length === 0) {
    return <EmptyChartMessage>No history yet.</EmptyChartMessage>;
  }
  const points = data.map((p) => {
    const total = p.available + p.underOffer + p.sold;
    if (mode === 'absolute') {
      return {
        month: formatMonth(p.month),
        available: p.available,
        underOffer: p.underOffer,
        sold: p.sold,
      };
    }
    if (total === 0) {
      return { month: formatMonth(p.month), available: 0, underOffer: 0, sold: 0 };
    }
    return {
      month: formatMonth(p.month),
      available: (p.available / total) * 100,
      underOffer: (p.underOffer / total) * 100,
      sold: (p.sold / total) * 100,
    };
  });

  return (
    <ResponsiveContainer width="100%" height={260}>
      <AreaChart data={points} margin={{ top: 8, right: 8, left: -16, bottom: 24 }}>
        <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
        <XAxis
          dataKey="month"
          tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }}
          interval="preserveStartEnd"
        />
        <YAxis
          tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }}
          tickFormatter={(v) => (mode === 'proportional' ? `${v}%` : String(v))}
        />
        <Tooltip
          contentStyle={{
            background: 'hsl(var(--popover))',
            border: '1px solid hsl(var(--border))',
            borderRadius: '6px',
            fontSize: 12,
          }}
          formatter={(value, name) => {
            const v = mode === 'proportional' ? `${(value as number).toFixed(1)}%` : String(value);
            return [
              v,
              name === 'sold' ? 'Sold' : name === 'underOffer' ? 'Under offer' : 'Available',
            ];
          }}
        />
        <Area type="monotone" dataKey="available" stackId="1" stroke="#cbd5e1" fill="#e2e8f0" />
        <Area type="monotone" dataKey="underOffer" stackId="1" stroke="#fbbf24" fill="#fde68a" />
        <Area type="monotone" dataKey="sold" stackId="1" stroke="#3a7bc8" fill="#b1cbe9" />
      </AreaChart>
    </ResponsiveContainer>
  );
}

function TenancyChurnChart({ points }: { points: TenancyChurnPoint[] }) {
  if (points.length === 0) {
    return <EmptyChartMessage>No tenancy events in the window.</EmptyChartMessage>;
  }
  return (
    <ResponsiveContainer width="100%" height={240}>
      <BarChart data={points} margin={{ top: 8, right: 8, left: -16, bottom: 24 }}>
        <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
        <XAxis dataKey="bucket" tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }} />
        <YAxis tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }} />
        <Tooltip
          contentStyle={{
            background: 'hsl(var(--popover))',
            border: '1px solid hsl(var(--border))',
            borderRadius: '6px',
            fontSize: 12,
          }}
        />
        <Bar dataKey="newActive" name="New active" stackId="a" fill="#3a7bc8" />
        <Bar dataKey="ended" name="Ended" stackId="b" fill="#f87171" />
      </BarChart>
    </ResponsiveContainer>
  );
}

function TenureHistogram({ rows }: { rows: TenureBucket[] }) {
  if (rows.length === 0 || rows.every((r) => r.count === 0)) {
    return <EmptyChartMessage>No completed tenancies yet.</EmptyChartMessage>;
  }
  return (
    <ResponsiveContainer width="100%" height={240}>
      <BarChart data={rows} margin={{ top: 8, right: 8, left: -16, bottom: 24 }}>
        <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
        <XAxis dataKey="label" tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }} />
        <YAxis
          tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }}
          allowDecimals={false}
        />
        <Tooltip
          contentStyle={{
            background: 'hsl(var(--popover))',
            border: '1px solid hsl(var(--border))',
            borderRadius: '6px',
            fontSize: 12,
          }}
        />
        <Bar dataKey="count" fill="#3a7bc8" radius={[4, 4, 0, 0]}>
          {rows.map((_, i) => (
            <Cell key={i} fill={['#94a3b8', '#6196d3', '#3a7bc8', '#1c4a87', '#0f172a'][i]} />
          ))}
        </Bar>
      </BarChart>
    </ResponsiveContainer>
  );
}

function OccupancyByAreaChart({ rows }: { rows: AreaOccupancyRow[] }) {
  if (rows.length === 0) {
    return <EmptyChartMessage>No areas yet.</EmptyChartMessage>;
  }
  return (
    <div className="space-y-2.5">
      {rows.map((r) => (
        <div
          key={r.area}
          className="grid items-center gap-3"
          style={{ gridTemplateColumns: '100px 1fr 60px' }}
        >
          <div className="text-sm font-medium text-foreground truncate">{r.area}</div>
          <div className="relative h-6 rounded-sm bg-muted/40 overflow-hidden flex">
            {r.sold > 0 && (
              <div
                className="bg-brand-500 h-full"
                style={{ width: `${(r.sold / r.total) * 100}%` }}
                title={`Sold: ${r.sold}`}
              />
            )}
            {r.underOffer > 0 && (
              <div
                className="bg-amber-400 h-full"
                style={{ width: `${(r.underOffer / r.total) * 100}%` }}
                title={`Under offer: ${r.underOffer}`}
              />
            )}
            {r.available > 0 && (
              <div
                className="bg-slate-300 h-full"
                style={{ width: `${(r.available / r.total) * 100}%` }}
                title={`Available: ${r.available}`}
              />
            )}
          </div>
          <span className="text-xs text-muted-foreground tabular-nums text-right">{r.total}</span>
        </div>
      ))}
    </div>
  );
}

function DocsPipelineChart({ rows }: { rows: DocsPipelineRow[] }) {
  if (rows.length === 0) {
    return <EmptyChartMessage>No documents yet.</EmptyChartMessage>;
  }
  return (
    <ResponsiveContainer width="100%" height={Math.max(120, rows.length * 50)}>
      <BarChart data={rows} layout="vertical" margin={{ top: 8, right: 8, left: 60, bottom: 24 }}>
        <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
        <XAxis type="number" tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }} />
        <YAxis
          type="category"
          dataKey="documentType"
          tick={{ fontSize: 11, fill: 'hsl(var(--muted-foreground))' }}
          width={120}
          tickFormatter={(v) => formatType(String(v))}
        />
        <Tooltip
          contentStyle={{
            background: 'hsl(var(--popover))',
            border: '1px solid hsl(var(--border))',
            borderRadius: '6px',
            fontSize: 12,
          }}
        />
        <Bar dataKey="draft" name="Draft" stackId="s" fill="#cbd5e1" />
        <Bar dataKey="sent" name="Sent" stackId="s" fill="#fbbf24" />
        <Bar dataKey="partiallySigned" name="Partially signed" stackId="s" fill="#fb923c" />
        <Bar dataKey="completed" name="Completed" stackId="s" fill="#10b981" />
        <Bar dataKey="declined" name="Declined" stackId="s" fill="#f87171" />
        <Bar dataKey="voided" name="Voided" stackId="s" fill="#94a3b8" />
      </BarChart>
    </ResponsiveContainer>
  );
}

// ─── Tables ──────────────────────────────────────────────────────────────────

function EndingSoonTable({ rows, portSlug }: { rows: EndingSoonRow[]; portSlug: string }) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Tenancies ending in next 6 months</CardTitle>
      </CardHeader>
      <CardContent>
        {rows.length === 0 ? (
          <p className="py-6 text-sm text-muted-foreground text-center">
            No tenancies ending soon.
          </p>
        ) : (
          <ul className="divide-y divide-border">
            {rows.map((r) => (
              <li key={r.id} className="py-2.5 flex items-center gap-3 text-sm">
                <Link
                  href={`/${portSlug}/tenancies/${r.id}` as Route}
                  className="font-medium text-foreground hover:text-primary flex-1 truncate"
                >
                  {r.clientName}
                  {r.primaryBerth ? (
                    <span className="text-muted-foreground"> · {r.primaryBerth}</span>
                  ) : null}
                </Link>
                <Badge variant="outline" className="text-[11px]">
                  {r.tenureType}
                </Badge>
                <span className="text-xs text-muted-foreground tabular-nums w-24 text-right">
                  {r.endDate.slice(0, 10)}
                </span>
                <span
                  className={cn(
                    'text-xs tabular-nums w-16 text-right',
                    r.daysUntilEnd < 30
                      ? 'text-rose-700 font-medium'
                      : r.daysUntilEnd < 60
                        ? 'text-amber-700'
                        : 'text-muted-foreground',
                  )}
                >
                  {r.daysUntilEnd}d
                </span>
              </li>
            ))}
          </ul>
        )}
      </CardContent>
    </Card>
  );
}

function VacantBerthsTable({ rows, portSlug }: { rows: VacantBerthRow[]; portSlug: string }) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Vacant berths (&gt;60 days)</CardTitle>
        <p className="text-xs text-muted-foreground">
          Available berths that haven&apos;t moved in 2+ months. The &quot;why isn&apos;t this
          selling&quot; list.
        </p>
      </CardHeader>
      <CardContent>
        {rows.length === 0 ? (
          <p className="py-6 text-sm text-muted-foreground text-center">No stagnant inventory.</p>
        ) : (
          <ul className="divide-y divide-border">
            {rows.map((r) => (
              <li key={r.id} className="py-2.5 flex items-center gap-3 text-sm">
                <Anchor className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden />
                <Link
                  href={`/${portSlug}/berths/${r.id}` as Route}
                  className="font-medium text-foreground hover:text-primary"
                >
                  {r.mooring}
                </Link>
                <span className="text-xs text-muted-foreground">{r.area ?? '—'}</span>
                <span className="text-xs text-muted-foreground">{r.dimensions}</span>
                <span className="flex-1 text-right tabular-nums">
                  {r.price !== null ? formatMoney(r.price, r.currency) : '—'}
                </span>
                <span className="text-xs text-muted-foreground tabular-nums w-20 text-right">
                  {r.daysAvailable !== null ? `${r.daysAvailable}d` : '—'}
                </span>
              </li>
            ))}
          </ul>
        )}
      </CardContent>
    </Card>
  );
}

function StuckSigningTable({ rows, portSlug }: { rows: StuckSigningRow[]; portSlug: string }) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Stuck signing</CardTitle>
        <p className="text-xs text-muted-foreground">
          Documents sent but not completed. Thresholds: EOI &gt; 10d / Reservation &gt; 7d /
          Contract &gt; 5d.
        </p>
      </CardHeader>
      <CardContent>
        {rows.length === 0 ? (
          <p className="py-6 text-sm text-muted-foreground text-center">
            Nothing stuck — signings on track.
          </p>
        ) : (
          <ul className="divide-y divide-border">
            {rows.map((r) => (
              <li key={r.id} className="py-2.5 flex items-center gap-3 text-sm">
                <FileWarning className="h-4 w-4 text-amber-600 shrink-0" aria-hidden />
                <Link
                  href={`/${portSlug}/documents/${r.id}` as Route}
                  className="font-medium text-foreground hover:text-primary flex-1 truncate"
                >
                  {r.title}
                </Link>
                <Badge variant="outline" className="text-[11px]">
                  {formatType(r.documentType)}
                </Badge>
                <span className="text-xs text-muted-foreground w-32 truncate text-right">
                  {r.clientName ?? '—'}
                </span>
                <span className="text-xs text-rose-700 tabular-nums w-20 text-right font-medium">
                  {r.daysOutstanding}d
                </span>
              </li>
            ))}
          </ul>
        )}
      </CardContent>
    </Card>
  );
}

function HighestValueVacantTable({
  rows,
  portSlug,
}: {
  rows: HighestValueVacantRow[];
  portSlug: string;
}) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Highest-value vacant berths</CardTitle>
        <p className="text-xs text-muted-foreground">
          Top {rows.length} available berths by price. Sales focus list.
        </p>
      </CardHeader>
      <CardContent>
        {rows.length === 0 ? (
          <p className="py-6 text-sm text-muted-foreground text-center">
            No priced vacant inventory.
          </p>
        ) : (
          <ul className="divide-y divide-border">
            {rows.map((r, i) => (
              <li key={r.id} className="py-2.5 flex items-center gap-3 text-sm">
                <span className="text-[10px] font-mono text-muted-foreground w-6 text-center">
                  {i + 1}
                </span>
                <Link
                  href={`/${portSlug}/berths/${r.id}` as Route}
                  className="font-medium text-foreground hover:text-primary"
                >
                  {r.mooring}
                </Link>
                <span className="text-xs text-muted-foreground">{r.area ?? '—'}</span>
                <span className="text-xs text-muted-foreground">{r.dimensions}</span>
                <span className="flex-1 text-right tabular-nums text-foreground font-medium">
                  {formatMoney(r.price, r.currency)}
                </span>
                <Button asChild variant="ghost" size="sm">
                  <Link href={`/${portSlug}/berths/${r.id}` as Route}>Open</Link>
                </Button>
              </li>
            ))}
          </ul>
        )}
      </CardContent>
    </Card>
  );
}

function EmptyChartMessage({ children }: { children: React.ReactNode }) {
  return <div className="py-10 text-center text-sm text-muted-foreground">{children}</div>;
}

function formatType(t: string): string {
  return t
    .replace(/_/g, ' ')
    .replace(/\b\w/g, (c) => c.toUpperCase())
    .replace(/Eoi/i, 'EOI');
}

function formatMonth(month: string): string {
  // "2026-04" → "Apr"
  const [year, m] = month.split('-');
  if (!year || !m) return month;
  const d = new Date(parseInt(year), parseInt(m) - 1, 1);
  return d.toLocaleDateString(undefined, { month: 'short' });
}

// Suppress unused-import warning for hooks used only at certain breakpoints
void useUIStore;
