'use client';

/**
 * Two-step wizard for bulk-creating berths during new-port setup.
 *
 * Step 1: pick the dock letter, the range (start..end mooring number),
 *   and the genuinely-standard defaults (tenure, status). Generates one
 *   empty row per mooring in the range.
 *
 * Step 2: editable table of the generated rows. Reps fill in per-row
 *   dimensions / pontoon / pricing. "Apply to all" inputs at the top
 *   of each column copy a value down. Validation is inline.
 *
 * Per PRE-DEPLOY-PLAN § 1.4.13. Drag-fill is a stretch - left as a
 * follow-up; keyboard-friendly "Apply to all" covers most of the
 * speed win without the complexity.
 */

import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { useParams, useRouter } from 'next/navigation';
import { Loader2, Trash2 } from 'lucide-react';
import { toast } from 'sonner';

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { useVocabulary } from '@/hooks/use-vocabulary';
import { CurrencySelect } from '@/components/shared/currency-select';

// Common dock-letter shorthand. Wizard accepts any uppercase letter
// sequence matching the canonical mooring regex (`^[A-Z]+$`) - these
// five are the most-frequently-used; reps add new ones via the
// "Custom" input below.
const COMMON_DOCK_LETTERS = ['A', 'B', 'C', 'D', 'E'] as const;
// The custom flow widens DockLetter beyond the shortlist; any uppercase
// string the rep types is valid as long as it matches the canonical
// `^[A-Z]+$` letter portion of a mooring number.
type DockLetter = string;

interface RowDraft {
  mooringNumber: string;
  area: string;
  status: 'available';
  tenureType: 'permanent' | 'fixed_term';
  lengthFt: string;
  widthFt: string;
  draftFt: string;
  sidePontoon: string;
  price: string;
  priceCurrency: string;
}

function genRange(letter: DockLetter, start: number, end: number): RowDraft[] {
  const out: RowDraft[] = [];
  for (let i = start; i <= end; i += 1) {
    out.push({
      mooringNumber: `${letter}${i}`,
      area: letter,
      status: 'available',
      tenureType: 'permanent',
      lengthFt: '',
      widthFt: '',
      draftFt: '',
      sidePontoon: '',
      price: '',
      priceCurrency: 'USD',
    });
  }
  return out;
}

export function BulkAddBerthsWizard() {
  const params = useParams<{ portSlug: string }>();
  const portSlug = params?.portSlug ?? '';
  const router = useRouter();
  // Canonical, admin-editable side-pontoon vocabulary (per-port overrides
  // honoured). Falls back to BERTH_SIDE_PONTOON_OPTIONS defaults when the
  // /api/v1/vocabularies request hasn't resolved yet.
  const sidePontoonOptions = useVocabulary('berth_side_pontoon_options');

  const [step, setStep] = useState<'sequence' | 'edit'>('sequence');
  // Unit the rep is entering dims in. Persisted only for the wizard's
  // lifetime; the underlying `RowDraft` always stores the value as a
  // raw string in this unit. Conversion to canonical feet happens
  // once at submit (1 m = 3.28084 ft).
  const [dimUnit, setDimUnit] = useState<'ft' | 'm'>('ft');
  const FT_PER_M = 3.28084;
  const inputToFt = (v: string): number | undefined => {
    if (!v) return undefined;
    const n = Number(v);
    if (!Number.isFinite(n)) return undefined;
    return dimUnit === 'm' ? n * FT_PER_M : n;
  };

  // Step 1 state
  const [letter, setLetter] = useState<DockLetter>('A');
  const [rangeStart, setRangeStart] = useState('1');
  const [rangeEnd, setRangeEnd] = useState('10');
  const [tenure, setTenure] = useState<'permanent' | 'fixed_term'>('permanent');

  // Step 2 state
  const [rows, setRows] = useState<RowDraft[]>([]);
  /** Mooring numbers already present in the port. Populated by the
   *  pre-flight check fired during Step 1 → Step 2 transition. */
  const [duplicates, setDuplicates] = useState<Set<string>>(new Set());
  const [checkingDups, setCheckingDups] = useState(false);

  async function handleGenerate() {
    // Validate the dock letter - must be one or more uppercase letters per
    // the canonical mooring regex. Custom-input path normalises to upper
    // already, but guard against an empty input.
    if (!letter || !/^[A-Z]+$/.test(letter)) {
      toast.error('Dock letter must be one or more uppercase letters (e.g. A, B, AA).');
      return;
    }
    const s = parseInt(rangeStart, 10);
    const e = parseInt(rangeEnd, 10);
    if (!Number.isFinite(s) || !Number.isFinite(e) || s < 0 || e < s) {
      toast.error('Invalid range');
      return;
    }
    if (e - s > 499) {
      toast.error('Cap is 500 berths per batch.');
      return;
    }
    const seeded = genRange(letter, s, e).map((r) => ({ ...r, tenureType: tenure }));

    // Pre-flight duplicate check: ask the server which of the generated
    // mooring numbers already exist as non-archived berths in this port.
    // Fail-open if the request errors (the bulk-add endpoint still
    // enforces uniqueness server-side; the pre-flight is a UX nicety).
    setCheckingDups(true);
    try {
      const res = await apiFetch<{ data: { duplicates: string[] } }>(
        '/api/v1/berths/check-duplicates',
        {
          method: 'POST',
          body: { mooringNumbers: seeded.map((r) => r.mooringNumber) },
        },
      );
      setDuplicates(new Set(res.data.duplicates));
      if (res.data.duplicates.length > 0) {
        toast.warning(
          `${res.data.duplicates.length} mooring number${
            res.data.duplicates.length === 1 ? '' : 's'
          } already exist in this port. They're flagged in Step 2.`,
        );
      }
    } catch {
      // Pre-flight failure is non-blocking: the user still proceeds and
      // the server's uniqueness constraint catches collisions at submit.
      setDuplicates(new Set());
    } finally {
      setCheckingDups(false);
    }
    setRows(seeded);
    setStep('edit');
  }

  /** Drop any rows whose mooring number is a known duplicate. */
  function removeAllDuplicates() {
    setRows((prev) => prev.filter((r) => !duplicates.has(r.mooringNumber)));
  }

  function setRowField<K extends keyof RowDraft>(idx: number, key: K, value: RowDraft[K]) {
    setRows((prev) => prev.map((r, i) => (i === idx ? { ...r, [key]: value } : r)));
  }

  function applyToAll<K extends keyof RowDraft>(key: K, value: RowDraft[K]) {
    setRows((prev) => prev.map((r) => ({ ...r, [key]: value })));
  }

  function removeRow(idx: number) {
    setRows((prev) => prev.filter((_, i) => i !== idx));
  }

  const mutation = useMutation({
    mutationFn: async () => {
      const payload = {
        berths: rows.map((r) => ({
          mooringNumber: r.mooringNumber,
          area: r.area,
          status: r.status,
          tenureType: r.tenureType,
          lengthFt: inputToFt(r.lengthFt),
          widthFt: inputToFt(r.widthFt),
          draftFt: inputToFt(r.draftFt),
          price: r.price ? Number(r.price) : undefined,
          priceCurrency: r.priceCurrency || undefined,
          sidePontoon: r.sidePontoon || undefined,
        })),
      };
      const res = await apiFetch<{ data: { inserted: number } }>('/api/v1/berths/bulk-add', {
        method: 'POST',
        body: payload,
      });
      return res.data;
    },
    onSuccess: (data) => {
      toast.success(`Created ${data.inserted} berths`);
      router.push(`/${portSlug}/berths`);
    },
    onError: (err) => toastError(err),
  });

  if (step === 'sequence') {
    return (
      <Card>
        <CardHeader>
          <CardTitle>Step 1 - Sequence</CardTitle>
          <CardDescription>
            Pick the dock letter and the mooring-number range. Tenure + status apply to every row;
            everything else (dimensions, pricing, pontoon) is filled per row in Step 2.
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-4">
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
            <div className="space-y-1">
              <Label>Dock letter</Label>
              {/* Common dock letters as quick-pick chips; "Custom…" reveals
                  a free-text input for ports whose dock layout extends
                  beyond A-E (rare but supported). Canonical mooring regex
                  is `^[A-Z]+\d+$`, so any uppercase letter sequence is
                  valid as the prefix. */}
              <div className="flex flex-wrap items-center gap-1">
                {COMMON_DOCK_LETTERS.map((l) => (
                  <Button
                    key={l}
                    type="button"
                    size="sm"
                    variant={letter === l ? 'default' : 'outline'}
                    className="h-9 w-9 p-0 font-mono"
                    onClick={() => setLetter(l)}
                  >
                    {l}
                  </Button>
                ))}
                <Input
                  value={(COMMON_DOCK_LETTERS as readonly string[]).includes(letter) ? '' : letter}
                  onChange={(e) => setLetter(e.target.value.toUpperCase().replace(/[^A-Z]/g, ''))}
                  placeholder="Other…"
                  aria-label="Custom dock letter"
                  maxLength={4}
                  className="h-9 w-20 font-mono"
                />
              </div>
              <p className="text-xs text-muted-foreground">
                Any uppercase letter sequence. Common ports use A-E; mark a custom letter when
                expanding to F+ or letter-pairs like AA.
              </p>
            </div>
            <div className="space-y-1">
              <Label>Range start</Label>
              <Input value={rangeStart} onChange={(e) => setRangeStart(e.target.value)} />
            </div>
            <div className="space-y-1">
              <Label>Range end</Label>
              <Input value={rangeEnd} onChange={(e) => setRangeEnd(e.target.value)} />
            </div>
            <div className="space-y-1">
              <Label>Tenure (all rows)</Label>
              <Select
                value={tenure}
                onValueChange={(v) => setTenure(v as 'permanent' | 'fixed_term')}
              >
                <SelectTrigger>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="permanent">Permanent</SelectItem>
                  <SelectItem value="fixed_term">Fixed term</SelectItem>
                </SelectContent>
              </Select>
            </div>
          </div>
          <p className="text-xs text-muted-foreground">
            Will generate {Math.max(0, parseInt(rangeEnd, 10) - parseInt(rangeStart, 10) + 1)} rows
            (e.g. {letter}
            {rangeStart} … {letter}
            {rangeEnd}).
          </p>
          <Button onClick={handleGenerate} disabled={checkingDups}>
            {checkingDups ? (
              <>
                <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" aria-hidden />
                Checking…
              </>
            ) : (
              'Continue →'
            )}
          </Button>
        </CardContent>
      </Card>
    );
  }

  const remainingDuplicates = rows.filter((r) => duplicates.has(r.mooringNumber));

  return (
    <Card>
      <CardHeader>
        <div className="flex items-start justify-between gap-3">
          <div>
            <CardTitle>Step 2 - Fill in each row</CardTitle>
            <CardDescription>
              Per-row dimensions, pricing, pontoon. Use the &ldquo;Apply to all&rdquo; inputs in the
              header to copy a value down every row at once.
            </CardDescription>
          </div>
          {/* Dimension-unit toggle. The wizard stores values as-entered;
              conversion to canonical feet (1 m = 3.28084 ft) happens once
              at submit. Switching mid-edit leaves existing inputs as
              numeric strings - the rep is responsible for re-entering if
              the unit interpretation just changed under them. */}
          <Button
            type="button"
            size="sm"
            variant="outline"
            onClick={() => setDimUnit(dimUnit === 'ft' ? 'm' : 'ft')}
            aria-label={`Switch dimension entry to ${dimUnit === 'ft' ? 'metres' : 'feet'}`}
            title={`Entering dimensions in ${dimUnit === 'ft' ? 'feet' : 'metres'} - click to switch`}
            className="font-mono text-xs"
          >
            {dimUnit}
          </Button>
        </div>
      </CardHeader>
      <CardContent>
        {remainingDuplicates.length > 0 ? (
          <div
            role="alert"
            className="mb-3 flex items-start justify-between gap-3 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-900"
          >
            <div>
              <div className="font-medium">
                {remainingDuplicates.length} duplicate{' '}
                {remainingDuplicates.length === 1 ? 'mooring number' : 'mooring numbers'} found
              </div>
              <div className="mt-0.5 text-xs">
                {remainingDuplicates
                  .slice(0, 8)
                  .map((r) => r.mooringNumber)
                  .join(', ')}
                {remainingDuplicates.length > 8
                  ? ` and ${remainingDuplicates.length - 8} more`
                  : ''}
                . Submit will fail on these rows. Remove them or change the range.
              </div>
            </div>
            <Button size="sm" variant="outline" onClick={removeAllDuplicates}>
              Remove all duplicates
            </Button>
          </div>
        ) : null}
        <div className="overflow-x-auto">
          <table className="w-full text-xs">
            <thead>
              <tr className="border-b text-left text-muted-foreground">
                <th scope="col" className="py-2 pr-2">
                  Mooring
                </th>
                <th scope="col" className="py-2 pr-2">
                  Length ({dimUnit})
                </th>
                <th scope="col" className="py-2 pr-2">
                  Width ({dimUnit})
                </th>
                <th scope="col" className="py-2 pr-2">
                  Draft ({dimUnit})
                </th>
                <th scope="col" className="py-2 pr-2">
                  Side pontoon
                </th>
                <th scope="col" className="py-2 pr-2">
                  Price
                </th>
                <th scope="col" className="py-2 pr-2">
                  Currency
                </th>
                <th scope="col" className="py-2 pr-2">
                  <span className="sr-only">Actions</span>
                </th>
              </tr>
              <tr className="border-b bg-muted/30">
                <td className="py-1 pr-2 text-xs text-muted-foreground">apply to all →</td>
                {(
                  [
                    ['lengthFt', 'number'],
                    ['widthFt', 'number'],
                    ['draftFt', 'number'],
                  ] as const
                ).map(([k, type]) => (
                  <td key={k} className="py-1 pr-2">
                    <Input
                      className="h-7 text-xs"
                      type={type}
                      onBlur={(e) => {
                        if (e.target.value) applyToAll(k, e.target.value);
                      }}
                      placeholder="all"
                    />
                  </td>
                ))}
                <td className="py-1 pr-2">
                  <Select
                    onValueChange={(v) => applyToAll('sidePontoon', v === '__none__' ? '' : v)}
                  >
                    <SelectTrigger className="h-7 text-xs">
                      <SelectValue placeholder="all" />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectItem value="__none__">(none)</SelectItem>
                      {sidePontoonOptions.filter(Boolean).map((p) => (
                        <SelectItem key={p} value={p}>
                          {p}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </td>
                <td className="py-1 pr-2">
                  <Input
                    className="h-7 text-xs"
                    type="number"
                    onBlur={(e) => {
                      if (e.target.value) applyToAll('price', e.target.value);
                    }}
                    placeholder="all"
                  />
                </td>
                <td className="py-1 pr-2">
                  <CurrencySelect
                    value={undefined}
                    onValueChange={(v) => applyToAll('priceCurrency', v)}
                    className="h-7 text-xs"
                  />
                </td>
                <td>
                  <span className="sr-only">Actions placeholder</span>
                </td>
              </tr>
            </thead>
            <tbody>
              {rows.map((row, idx) => {
                const isDup = duplicates.has(row.mooringNumber);
                return (
                  <tr
                    key={row.mooringNumber}
                    className={
                      isDup ? 'border-b last:border-b-0 bg-amber-50/60' : 'border-b last:border-b-0'
                    }
                  >
                    <td className="py-1 pr-2 font-medium">
                      {row.mooringNumber}
                      {isDup ? (
                        <span className="ml-1 inline-flex items-center rounded-sm bg-amber-200/70 px-1 text-xs font-semibold uppercase tracking-wide text-amber-900">
                          Dup
                        </span>
                      ) : null}
                    </td>
                    <td className="py-1 pr-2">
                      <Input
                        className="h-7 text-xs"
                        type="number"
                        value={row.lengthFt}
                        onChange={(e) => setRowField(idx, 'lengthFt', e.target.value)}
                      />
                    </td>
                    <td className="py-1 pr-2">
                      <Input
                        className="h-7 text-xs"
                        type="number"
                        value={row.widthFt}
                        onChange={(e) => setRowField(idx, 'widthFt', e.target.value)}
                      />
                    </td>
                    <td className="py-1 pr-2">
                      <Input
                        className="h-7 text-xs"
                        type="number"
                        value={row.draftFt}
                        onChange={(e) => setRowField(idx, 'draftFt', e.target.value)}
                      />
                    </td>
                    <td className="py-1 pr-2">
                      <Select
                        value={row.sidePontoon || '__none__'}
                        onValueChange={(v) =>
                          setRowField(idx, 'sidePontoon', v === '__none__' ? '' : v)
                        }
                      >
                        <SelectTrigger className="h-7 text-xs">
                          <SelectValue />
                        </SelectTrigger>
                        <SelectContent>
                          <SelectItem value="__none__"> - </SelectItem>
                          {sidePontoonOptions.filter(Boolean).map((p) => (
                            <SelectItem key={p} value={p}>
                              {p}
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                    </td>
                    <td className="py-1 pr-2">
                      <Input
                        className="h-7 text-xs"
                        type="number"
                        value={row.price}
                        onChange={(e) => setRowField(idx, 'price', e.target.value)}
                      />
                    </td>
                    <td className="py-1 pr-2">
                      <CurrencySelect
                        value={row.priceCurrency || undefined}
                        onValueChange={(v) => setRowField(idx, 'priceCurrency', v)}
                        className="h-7 w-24 text-xs"
                      />
                    </td>
                    <td className="py-1 pr-2">
                      <Button
                        size="sm"
                        variant="ghost"
                        className="h-7 w-7 p-0"
                        onClick={() => removeRow(idx)}
                        aria-label={`Remove ${row.mooringNumber}`}
                      >
                        <Trash2 className="size-3.5" aria-hidden />
                      </Button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>

        <div className="mt-4 flex items-center justify-between">
          <Button variant="ghost" onClick={() => setStep('sequence')} disabled={mutation.isPending}>
            ← Back
          </Button>
          <Button
            disabled={mutation.isPending || rows.length === 0 || remainingDuplicates.length > 0}
            onClick={() => mutation.mutate()}
            title={
              remainingDuplicates.length > 0
                ? 'Resolve the duplicate mooring numbers before submitting.'
                : undefined
            }
          >
            {mutation.isPending ? (
              <Loader2 className="size-4 animate-spin" aria-hidden />
            ) : (
              `Create ${rows.length} berths`
            )}
          </Button>
        </div>
      </CardContent>
    </Card>
  );
}
