'use client';

/**
 * Shared send-document dialog (Phase 7).
 *
 * Used by:
 *   - {@link SendBerthPdfDialog} (berths/) - single berth, recipient picker.
 *   - {@link SendBrochureDialog}    (clients/, interests/) - brochure picker.
 *   - The interest "send from interest" pattern reuses both via a wrapper.
 *
 * §14.7 mitigations enforced client-side:
 *   - Recipient email is shown verbatim in the confirm step (no quick-send).
 *   - Pre-send dry-run calls /preview first; the Send button is disabled
 *     until the unresolved-tokens list is empty.
 *   - Body length capped at 50KB; char count visible.
 */

import { useMemo, useState } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
import DOMPurify from 'isomorphic-dompurify';

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { TrackedLinkComposerButton } from '@/components/email/tracked-link-composer-button';
import { Input } from '@/components/ui/input';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';

const BODY_MAX = 50_000;

export type DocumentKind = 'berth_pdf' | 'brochure';

interface SendDocumentDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  documentKind: DocumentKind;
  /** Pre-filled recipient. Leave both blank to let the rep type one. */
  recipient: { clientId?: string; email?: string; interestId?: string };
  /** Either a berthId (for berth_pdf) or brochureId (for brochure). */
  context: { berthId?: string; brochureId?: string };
  /** Title displayed in the dialog header. */
  title: string;
  /** Short context line under the title (e.g. "Berth A1 - primary version"). */
  subtitle?: string;
  onSent?: () => void;
}

interface PreviewResponse {
  data: {
    html: string;
    markdown: string;
    unresolved: string[];
    attachmentThresholdMb: number;
  };
}

export function SendDocumentDialog(props: SendDocumentDialogProps) {
  // Key-based remount: the body is keyed on `open + recipient.email` so
  // its useState initializers re-run each time the dialog opens with a
  // new recipient. Replaces the prior useEffect-driven reset that the
  // Compiler flagged as set-state-in-effect.
  return (
    <SendDocumentDialogInner
      key={props.open ? `open:${props.recipient.email ?? ''}` : 'closed'}
      {...props}
    />
  );
}

function SendDocumentDialogInner({
  open,
  onOpenChange,
  documentKind,
  recipient,
  context,
  title,
  subtitle,
  onSent,
}: SendDocumentDialogProps) {
  const [step, setStep] = useState<'compose' | 'confirm'>('compose');
  const [emailOverride, setEmailOverride] = useState(recipient.email ?? '');
  const [customBody, setCustomBody] = useState('');

  const recipientForApi = useMemo(
    () => ({
      clientId: recipient.clientId,
      email: emailOverride || recipient.email,
      interestId: recipient.interestId,
    }),
    [recipient.clientId, recipient.email, recipient.interestId, emailOverride],
  );

  // Live preview via /api/v1/document-sends/preview. Re-runs whenever the
  // body text or recipient changes (debounce-by-react-query for free).
  // The preview also surfaces the per-port attachment-size threshold so
  // the rep can see up-front whether their attachment will go inline vs
  // as a 24h download link.
  const previewQuery = useQuery<PreviewResponse>({
    queryKey: [
      'document-sends-preview',
      documentKind,
      context.berthId ?? null,
      context.brochureId ?? null,
      recipientForApi.clientId ?? null,
      recipientForApi.email ?? null,
      customBody,
    ],
    queryFn: () =>
      apiFetch('/api/v1/document-sends/preview', {
        method: 'POST',
        body: {
          documentKind,
          recipient: recipientForApi,
          berthId: context.berthId,
          brochureId: context.brochureId,
          customBodyMarkdown: customBody.trim() ? customBody : undefined,
        },
      }),
    enabled: open && Boolean(recipientForApi.clientId || recipientForApi.email),
  });

  type SendResp = { data: { error?: string; deliveredAsAttachment: boolean } };
  const sendMutation = useMutation<SendResp, Error, void>({
    mutationFn: async (): Promise<SendResp> => {
      const endpoint =
        documentKind === 'berth_pdf'
          ? '/api/v1/document-sends/berth-pdf'
          : '/api/v1/document-sends/brochure';
      const body =
        documentKind === 'berth_pdf'
          ? {
              berthId: context.berthId,
              recipient: recipientForApi,
              customBodyMarkdown: customBody.trim() ? customBody : undefined,
            }
          : {
              brochureId: context.brochureId,
              recipient: recipientForApi,
              customBodyMarkdown: customBody.trim() ? customBody : undefined,
            };
      return (await apiFetch<SendResp>(endpoint, { method: 'POST', body })) as SendResp;
    },
    onSuccess: (resp) => {
      if (resp.data.error) {
        toast.error(`Send failed: ${resp.data.error}`);
      } else {
        toast.success(
          resp.data.deliveredAsAttachment
            ? 'Sent as attachment'
            : 'Sent (large file delivered as download link)',
        );
        onSent?.();
        onOpenChange(false);
      }
    },
    onError: (err) => {
      toastError(err);
    },
  });

  const unresolved = previewQuery.data?.data.unresolved ?? [];
  // Defense-in-depth: server-side `renderEmailBody()` already escapes
  // and allow-lists tags, but inject through DOMPurify before mounting
  // the preview into the DOM so a future server-side regression can't
  // silently produce a client-side XSS via the preview surface.
  const previewHtml = useMemo(
    () =>
      DOMPurify.sanitize(previewQuery.data?.data.html ?? '', {
        ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'a'],
        ALLOWED_ATTR: ['href', 'target', 'rel'],
        ALLOWED_URI_REGEXP: /^https:|^mailto:/i,
      }),
    [previewQuery.data?.data.html],
  );

  const recipientResolved = Boolean(recipientForApi.clientId || recipientForApi.email);
  const canPreview = recipientResolved;
  const canSend = step === 'confirm' && unresolved.length === 0 && !sendMutation.isPending;

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-3xl">
        <DialogHeader>
          <DialogTitle>{title}</DialogTitle>
          {subtitle && <DialogDescription>{subtitle}</DialogDescription>}
        </DialogHeader>

        {step === 'compose' ? (
          <div className="space-y-4">
            <div className="space-y-1">
              <Label htmlFor="ds-email">Recipient email</Label>
              <Input
                id="ds-email"
                type="email"
                value={emailOverride}
                onChange={(e) => setEmailOverride(e.target.value)}
                placeholder={recipient.email ? '' : 'recipient@example.com'}
              />
              <p className="text-xs text-muted-foreground">
                {recipient.clientId
                  ? 'Defaults to the client primary email; override here if needed.'
                  : 'Type the address you want to send to.'}
              </p>
            </div>

            {previewQuery.data?.data.attachmentThresholdMb !== undefined && (
              <p className="rounded border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-700">
                Files over <strong>{previewQuery.data.data.attachmentThresholdMb} MB</strong> are
                sent as a 24-hour download link instead of an inline attachment.
              </p>
            )}

            <div className="space-y-1">
              <div className="flex items-center justify-between gap-2">
                <Label htmlFor="ds-body">Message body</Label>
                {/* Tracked-link composer - appends a trackable redirect
                    URL to the body so click-throughs reconcile back to
                    the send's analytics. */}
                <TrackedLinkComposerButton
                  onInsert={(url) =>
                    setCustomBody((prev) => (prev ? `${prev}\n\n${url}` : url).slice(0, BODY_MAX))
                  }
                />
              </div>
              <Textarea
                id="ds-body"
                rows={10}
                value={customBody}
                onChange={(e) => setCustomBody(e.target.value.slice(0, BODY_MAX))}
                placeholder="Leave blank to use the port template…"
                className="font-mono text-sm"
              />
              <div className="flex items-center justify-between text-xs text-muted-foreground">
                <span>Markdown supported. Tokens like {'{{client.fullName}}'} are expanded.</span>
                <span>
                  {customBody.length} / {BODY_MAX}
                </span>
              </div>
            </div>

            {canPreview && previewQuery.isSuccess && (
              <div className="rounded border bg-muted/30 p-3">
                <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                  Preview
                </p>
                <div
                  className="prose prose-sm max-w-none"
                  dangerouslySetInnerHTML={{ __html: previewHtml }}
                />
                {unresolved.length > 0 && (
                  <p className="mt-3 rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-900">
                    Unresolved merge fields: {unresolved.join(', ')}. Replace these before sending.
                  </p>
                )}
              </div>
            )}
            {previewQuery.isLoading && canPreview && (
              <div className="flex items-center gap-2 text-sm text-muted-foreground">
                <Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Rendering preview…
              </div>
            )}
          </div>
        ) : (
          <div className="space-y-3">
            <div className="rounded border bg-muted/30 p-3 text-sm">
              <p>
                Sending to: <span className="font-mono">{recipientForApi.email}</span>
              </p>
              {context.berthId && <p>Document: berth PDF</p>}
              {context.brochureId !== undefined && <p>Document: brochure</p>}
            </div>
            <div
              className="prose prose-sm max-w-none rounded border p-3"
              dangerouslySetInnerHTML={{ __html: previewHtml }}
            />
            <p className="text-xs text-muted-foreground">
              The PDF file is added by the system after the body - your text won&rsquo;t override
              it.
            </p>
          </div>
        )}

        <DialogFooter className="gap-2">
          {step === 'compose' ? (
            <>
              <Button variant="outline" onClick={() => onOpenChange(false)}>
                Cancel
              </Button>
              <Button
                onClick={() => setStep('confirm')}
                disabled={!recipientResolved || unresolved.length > 0 || previewQuery.isLoading}
              >
                Review & send
              </Button>
            </>
          ) : (
            <>
              <Button variant="outline" onClick={() => setStep('compose')}>
                Back
              </Button>
              <Button onClick={() => sendMutation.mutate()} disabled={!canSend}>
                {sendMutation.isPending && (
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
                )}
                Confirm and send
              </Button>
            </>
          )}
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
