'use client';

import { useMemo, useState } from 'react';
import Link from 'next/link';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { AlertTriangle, ExternalLink, FileSignature, Pencil } from 'lucide-react';

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Label } from '@/components/ui/label';
import { Skeleton } from '@/components/ui/skeleton';
import { apiFetch } from '@/lib/api/client';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/ui-store';
import { Input } from '@/components/ui/input';
import { CountryCombobox } from '@/components/shared/country-combobox';
import { toastError } from '@/lib/api/toast-error';

const DOCUMENSO_TEMPLATE_VALUE = 'documenso-template';

interface InAppTemplate {
  id: string;
  name: string;
  description?: string | null;
  templateType: string;
}

interface EoiContextResponse {
  data: {
    client: {
      id: string;
      fullName: string;
      nationality: string | null;
      primaryEmail: string | null;
      primaryPhone: string | null;
      address: { street: string; city: string; country: string } | null;
    };
    yacht: {
      id: string;
      name: string;
      lengthFt: string | null;
      widthFt: string | null;
      draftFt: string | null;
      hullNumber: string | null;
      flag: string | null;
    } | null;
    berth: {
      mooringNumber: string;
      area: string | null;
      lengthFt: string | null;
    } | null;
    eoiBerthRange: string;
    port: { name: string };
  };
}

interface EoiGenerateDialogProps {
  interestId: string;
  /** Used to wire the "Edit on client" deep-link inside the dialog. */
  clientId?: string | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
}

/**
 * Pre-flight preview + generate dialog. Replaces the earlier ✓/✗
 * checklist with the actual values that will be auto-filled into the
 * EOI, so a sales rep can spot a wrong email or missing yacht
 * dimensions before sending the document for signing.
 *
 * Editing is deliberately routed back to the canonical client / yacht
 * detail pages (via "Edit on client" links) rather than allowing
 * inline edits inside the dialog. Why: the EOI's source-of-truth is
 * the underlying records, and forking the edit surface here would
 * silently bypass tag/audit-log/permission flows that the entity
 * detail pages enforce.
 */
export function EoiGenerateDialog({
  interestId,
  clientId,
  open,
  onOpenChange,
}: EoiGenerateDialogProps) {
  const queryClient = useQueryClient();
  const portSlug = useUIStore((s) => s.currentPortSlug);
  const [isGenerating, setIsGenerating] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [selectedTemplate, setSelectedTemplate] = useState<string>(DOCUMENSO_TEMPLATE_VALUE);

  // Resolved EOI context — the actual values the document will be
  // auto-filled with. Loaded only while the dialog is open so we don't
  // pay for the join tree on every interest detail page render.
  const { data: ctxRes, isLoading: ctxLoading } = useQuery<EoiContextResponse>({
    queryKey: ['interests', interestId, 'eoi-context'],
    queryFn: () => apiFetch<EoiContextResponse>(`/api/v1/interests/${interestId}/eoi-context`),
    enabled: open,
    staleTime: 30_000,
  });

  const ctx = ctxRes?.data;

  const { data: templatesRes } = useQuery<{ data: InAppTemplate[] }>({
    queryKey: ['document-templates', { templateType: 'eoi', isActive: true }],
    queryFn: () =>
      apiFetch<{ data: InAppTemplate[] }>(
        '/api/v1/document-templates?templateType=eoi&isActive=true',
      ),
    enabled: open,
  });

  const inAppTemplates = useMemo(() => templatesRes?.data ?? [], [templatesRes]);

  async function patchClient(body: Record<string, unknown>) {
    if (!ctx) return;
    await apiFetch(`/api/v1/clients/${ctx.client.id}`, { method: 'PATCH', body });
    queryClient.invalidateQueries({ queryKey: ['interests', interestId, 'eoi-context'] });
  }
  async function patchYacht(body: Record<string, unknown>) {
    if (!ctx?.yacht) return;
    await apiFetch(`/api/v1/yachts/${ctx.yacht.id}`, { method: 'PATCH', body });
    queryClient.invalidateQueries({ queryKey: ['interests', interestId, 'eoi-context'] });
  }

  // Required for the EOI's top paragraph (Section 2). Without these
  // the document is unsignable, so generation is blocked.
  const required = ctx
    ? [
        {
          key: 'name',
          label: 'Full name',
          value: ctx.client.fullName,
          present: !!ctx.client.fullName,
          edit: {
            onSave: async (next: string | null) => await patchClient({ fullName: next ?? '' }),
            placeholder: 'Full legal name',
          },
        },
        {
          key: 'nationality',
          label: 'Nationality',
          value: ctx.client.nationality,
          present: !!ctx.client.nationality,
          edit: {
            variant: 'country' as const,
            onSave: async (next: string | null) => {
              // Country combobox emits the ISO code; the read-only string is the
              // localised country name (resolved server-side). Coerce here so we
              // store the canonical ISO.
              const iso = next ? (next as string).toUpperCase() : null;
              await patchClient({ nationalityIso: iso });
            },
          },
        },
        {
          key: 'email',
          label: 'Email address',
          value: ctx.client.primaryEmail ?? null,
          present: !!ctx.client.primaryEmail,
        },
        {
          key: 'address',
          label: 'Address',
          value: ctx.client.address
            ? [ctx.client.address.street, ctx.client.address.city, ctx.client.address.country]
                .filter(Boolean)
                .join(', ')
            : null,
          present: !!ctx.client.address,
        },
      ]
    : [];

  // Optional — Section 3 of the EOI. Generation proceeds without them.
  const optional = ctx
    ? [
        {
          key: 'yacht',
          label: 'Yacht name',
          value: ctx.yacht?.name ?? null,
          edit: ctx.yacht
            ? {
                onSave: async (next: string | null) => await patchYacht({ name: next ?? '' }),
                placeholder: 'Yacht name',
              }
            : undefined,
        },
        {
          key: 'dimensions',
          label: 'Dimensions (L × W × D, ft)',
          value: ctx.yacht
            ? [ctx.yacht.lengthFt, ctx.yacht.widthFt, ctx.yacht.draftFt]
                .map((v) => v ?? '—')
                .join(' × ')
            : null,
        },
        {
          key: 'berth',
          label: 'Berth (primary mooring)',
          value: ctx.berth?.mooringNumber ?? null,
        },
        {
          key: 'berthRange',
          label: 'Berth bundle range',
          value: ctx.eoiBerthRange || null,
        },
        {
          key: 'phone',
          label: 'Phone',
          value: ctx.client.primaryPhone ?? null,
        },
      ]
    : [];

  const requiredMet = required.length > 0 && required.every((r) => r.present);

  async function handleGenerate() {
    if (!requiredMet) return;
    setIsGenerating(true);
    setError(null);
    try {
      const isDocumenso = selectedTemplate === DOCUMENSO_TEMPLATE_VALUE;
      const url = `/api/v1/document-templates/${encodeURIComponent(selectedTemplate)}/generate-and-sign`;
      await apiFetch(url, {
        method: 'POST',
        body: {
          interestId,
          pathway: isDocumenso ? 'documenso-template' : 'inapp',
          // Signers derived server-side from EOI context for both pathways.
          signers: [],
        },
      });
      queryClient.invalidateQueries({
        predicate: (q) => q.queryKey[0] === 'documents',
      });
      onOpenChange(false);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to generate EOI');
    } finally {
      setIsGenerating(false);
    }
  }

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-lg">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <FileSignature className="size-4" />
            Generate Expression of Interest
          </DialogTitle>
          <DialogDescription>
            Review the values that will be auto-filled into the EOI. Anything wrong? Edit it on the
            client&apos;s record before generating.
          </DialogDescription>
        </DialogHeader>

        <div className="space-y-4 py-1">
          <div className="space-y-2">
            <Label htmlFor="eoi-template">Template</Label>
            <Select value={selectedTemplate} onValueChange={setSelectedTemplate}>
              <SelectTrigger id="eoi-template">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value={DOCUMENSO_TEMPLATE_VALUE}>
                  Standard EOI — sent for e-signature (recommended)
                </SelectItem>
                {inAppTemplates.map((t) => (
                  <SelectItem key={t.id} value={t.id}>
                    {t.name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          {ctxLoading ? (
            <div className="space-y-2">
              <Skeleton className="h-4 w-full" />
              <Skeleton className="h-4 w-3/4" />
              <Skeleton className="h-4 w-2/3" />
            </div>
          ) : ctx ? (
            <div className="space-y-3 rounded-md border bg-muted/20 p-3">
              <div className="space-y-1">
                <p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
                  Required (Section 2 of the EOI)
                </p>
                <dl className="space-y-1.5">
                  {required.map((row) => (
                    <PreviewRow
                      key={row.key}
                      label={row.label}
                      // Nationality stores the localised country name in the preview
                      // but commits the ISO. Pass the underlying ISO via a closure
                      // so the CountryCombobox can highlight it correctly.
                      value={row.value}
                      missing={!row.present}
                      edit={row.edit}
                    />
                  ))}
                </dl>
              </div>
              <div className="space-y-1 border-t pt-2">
                <p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
                  Optional (Section 3 — left blank if absent)
                </p>
                <dl className="space-y-1.5">
                  {optional.map((row) => (
                    <PreviewRow key={row.key} label={row.label} value={row.value} edit={row.edit} />
                  ))}
                </dl>
              </div>
              {portSlug && clientId && (
                <div className="border-t pt-2 space-y-1">
                  <p className="text-[11px] text-muted-foreground">
                    Editing name / nationality / yacht name above patches the underlying records
                    directly. For phone, address, or to manage linked berths, jump to the canonical
                    page:
                  </p>
                  <div className="flex flex-wrap gap-3">
                    <Link
                      // eslint-disable-next-line @typescript-eslint/no-explicit-any
                      href={`/${portSlug}/clients/${clientId}` as any}
                      className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
                      onClick={() => onOpenChange(false)}
                    >
                      <Pencil className="size-3" />
                      Edit client details
                      <ExternalLink className="size-3" />
                    </Link>
                    <Link
                      // eslint-disable-next-line @typescript-eslint/no-explicit-any
                      href={`/${portSlug}/interests/${interestId}` as any}
                      className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
                      onClick={() => onOpenChange(false)}
                    >
                      <Pencil className="size-3" />
                      Manage linked berths
                      <ExternalLink className="size-3" />
                    </Link>
                  </div>
                </div>
              )}
            </div>
          ) : (
            <p className="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
              Couldn&apos;t load the EOI preview data. Try closing and reopening the dialog.
            </p>
          )}

          {!ctxLoading && ctx && !requiredMet && (
            <p className="flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
              <AlertTriangle className="size-3.5 shrink-0 mt-0.5" />
              Add the missing required details on the client&apos;s record before generating the
              EOI.
            </p>
          )}
        </div>

        {error && <p className="text-sm text-destructive">{error}</p>}

        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)} disabled={isGenerating}>
            Cancel
          </Button>
          <Button onClick={handleGenerate} disabled={!requiredMet || isGenerating || ctxLoading}>
            {isGenerating ? 'Generating…' : 'Generate EOI'}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function PreviewRow({
  label,
  value,
  missing = false,
  edit,
}: {
  label: string;
  value: string | null;
  missing?: boolean;
  /** When provided, renders a pencil affordance that opens an inline editor.
   *  The save handler is owned by the row config so each field can hit the
   *  right API (clients PATCH, yachts PATCH, …). */
  edit?: {
    onSave: (next: string | null) => Promise<void>;
    variant?: 'text' | 'country';
    placeholder?: string;
  };
}) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value ?? '');
  const [saving, setSaving] = useState(false);

  async function commit(next: string) {
    const trimmed = next.trim();
    if (!edit) return;
    if (trimmed === (value ?? '')) {
      setEditing(false);
      return;
    }
    setSaving(true);
    try {
      await edit.onSave(trimmed === '' ? null : trimmed);
      setEditing(false);
    } catch (err) {
      toastError(err);
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="flex items-baseline gap-2 text-sm">
      <dt className="w-32 shrink-0 text-xs text-muted-foreground">{label}</dt>
      <dd
        className={cn(
          'flex-1 wrap-break-word inline-flex items-center gap-2',
          missing
            ? 'text-rose-700 font-medium'
            : value
              ? 'text-foreground'
              : 'text-muted-foreground italic',
        )}
      >
        {edit && editing ? (
          edit.variant === 'country' ? (
            <CountryCombobox
              value={value}
              onChange={(iso) => void commit(iso ?? '')}
              defaultOpen
              onOpenChange={(o) => !o && setEditing(false)}
              compact={false}
              className="h-7 w-full"
            />
          ) : (
            <Input
              value={draft}
              onChange={(e) => setDraft(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter') void commit(draft);
                if (e.key === 'Escape') {
                  setDraft(value ?? '');
                  setEditing(false);
                }
              }}
              onBlur={() => !saving && void commit(draft)}
              placeholder={edit.placeholder}
              autoFocus
              disabled={saving}
              className="h-7 text-sm"
            />
          )
        ) : (
          <>
            <span className="flex-1">{value ?? (missing ? 'Missing — required' : 'Not set')}</span>
            {edit ? (
              <button
                type="button"
                onClick={() => {
                  setDraft(value ?? '');
                  setEditing(true);
                }}
                className="ml-1 inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground"
                aria-label={`Edit ${label}`}
              >
                <Pencil className="h-3 w-3" />
              </button>
            ) : null}
          </>
        )}
      </dd>
    </div>
  );
}
