'use client';

import { useEffect, useMemo, useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { Loader2, Plus, X, ChevronsUpDown, Check } from 'lucide-react';
import { z } from 'zod';

import { useFormScrollToError } from '@/hooks/use-form-scroll-to-error';
import { FormErrorSummary } from '@/components/forms/form-error-summary';

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { DatePicker } from '@/components/ui/date-picker';
import { Textarea } from '@/components/ui/textarea';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from '@/components/ui/sheet';
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { TagPicker } from '@/components/shared/tag-picker';
import { CountryCombobox } from '@/components/shared/country-combobox';
import { SubdivisionCombobox } from '@/components/shared/subdivision-combobox';
import { ClientForm } from '@/components/clients/client-form';
import { YachtForm } from '@/components/yachts/yacht-form';
import { InterestForm } from '@/components/interests/interest-form';
import { apiFetch } from '@/lib/api/client';
import { useEntityOptions } from '@/hooks/use-entity-options';
import { toastError } from '@/lib/api/toast-error';
import { createCompanySchema, type CreateCompanyInput } from '@/lib/validators/companies';
import type { CountryCode } from '@/lib/i18n/countries';
import { cn } from '@/lib/utils';

type CompanyStatus = 'active' | 'dissolved';

type CompanyFormValues = z.input<typeof createCompanySchema>;

interface CompanyFormProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** If provided, form is in edit mode */
  company?: {
    id: string;
    name: string;
    legalName: string | null;
    taxId: string | null;
    registrationNumber: string | null;
    incorporationCountryIso: string | null;
    incorporationSubdivisionIso: string | null;
    incorporationDate: string | null;
    status: string;
    billingEmail: string | null;
    notes: string | null;
  };
  /**
   * Optional initial values for the create flow - used by the global
   * command-search quick-create ("New company 'matthew'" → lands on
   * `/companies?create=1&prefill_name=matthew`). Ignored in edit mode.
   */
  prefill?: {
    name?: string;
  };
}

export function CompanyForm({ open, onOpenChange, company, prefill }: CompanyFormProps) {
  const queryClient = useQueryClient();
  const router = useRouter();
  const isEdit = !!company;
  const [formError, setFormError] = useState<string | null>(null);
  // Connection state - only used in create mode. Editing companies is done
  // from the detail page where members + yachts have their own tabs that
  // know how to handle removal / reassignment cleanly.
  const [attachedClientIds, setAttachedClientIds] = useState<string[]>([]);
  const [attachedYachtIds, setAttachedYachtIds] = useState<string[]>([]);
  const [clientFormOpen, setClientFormOpen] = useState(false);
  const [yachtFormOpen, setYachtFormOpen] = useState(false);
  // After successful save the dialog flow can branch: ask the rep whether to
  // also attach the picked clients' yachts (when any of them own yachts), and
  // optionally chain to a New Interest form pre-filled with one of the
  // attached clients.
  const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(null);
  const [pendingYachtPullIn, setPendingYachtPullIn] = useState<
    { yachtId: string; yachtName: string }[] | null
  >(null);
  // Reserved for the inverse pull-in (attached yacht → owner client). Wired
  // through but the inferring query is deferred - owner history isn't yet
  // surfaced cheaply via the yacht endpoint.
  // const [pendingOwnerPullIn, setPendingOwnerPullIn] = useState<...>(null);
  const [createInterestFor, setCreateInterestFor] = useState<string | null>(null);

  const {
    register,
    handleSubmit,
    watch,
    setValue,
    reset,
    control,
    formState: { errors, isSubmitting },
  } = useForm<CompanyFormValues>({
    resolver: zodResolver(createCompanySchema),
    defaultValues: {
      name: '',
      status: 'active',
      tagIds: [],
    },
  });

  const scrollOnError = useFormScrollToError(handleSubmit, errors);
  const tagIds = watch('tagIds') ?? [];
  const status = watch('status') ?? 'active';

  // Populate form when editing, or reset to defaults in create mode.
  useEffect(() => {
    if (company && open) {
      reset({
        name: company.name,
        legalName: company.legalName ?? undefined,
        taxId: company.taxId ?? undefined,
        registrationNumber: company.registrationNumber ?? undefined,
        incorporationCountryIso: company.incorporationCountryIso ?? undefined,
        incorporationSubdivisionIso: company.incorporationSubdivisionIso ?? undefined,
        incorporationDate: company.incorporationDate
          ? new Date(company.incorporationDate)
          : undefined,
        status: (company.status as CompanyStatus) ?? 'active',
        billingEmail: company.billingEmail ?? undefined,
        notes: company.notes ?? undefined,
        tagIds: [],
      });
    } else if (!company && open) {
      reset({ name: prefill?.name ?? '', status: 'active', tagIds: [] });
    }
    setFormError(null);
  }, [company, open, reset, prefill]);

  const mutation = useMutation({
    mutationFn: async (data: CreateCompanyInput) => {
      if (isEdit) {
        // updateCompanySchema omits tagIds - strip them from PATCH body.
        const { tagIds: _tIds, ...rest } = data;
        void _tIds;
        await apiFetch(`/api/v1/companies/${company!.id}`, {
          method: 'PATCH',
          body: rest,
        });
        return null;
      }
      const res = await apiFetch<{ data: { id: string } }>('/api/v1/companies', {
        method: 'POST',
        body: data,
      });
      const newCompanyId = res.data.id;
      // Connect each attached client as a company member. Failures collected
      // here surface as a toast but don't roll back the company create - the
      // rep can fix individual mismatches from the company detail page.
      for (const clientId of attachedClientIds) {
        try {
          await apiFetch(`/api/v1/companies/${newCompanyId}/members`, {
            method: 'POST',
            body: { clientId, role: 'member' },
          });
        } catch (err) {
          toastError(err);
        }
      }
      // Transfer ownership of each attached yacht to the company. This uses
      // the existing yacht-transfer endpoint so the audit log + ownership
      // history records the change just like a manual transfer would.
      for (const yachtId of attachedYachtIds) {
        try {
          await apiFetch(`/api/v1/yachts/${yachtId}/transfer`, {
            method: 'POST',
            body: {
              newOwner: { type: 'company', id: newCompanyId },
              reason: 'Attached during company creation',
            },
          });
        } catch (err) {
          toastError(err);
        }
      }
      return newCompanyId;
    },
    onSuccess: async (newCompanyId) => {
      queryClient.invalidateQueries({ queryKey: ['companies'] });
      if (isEdit || !newCompanyId) {
        onOpenChange(false);
        return;
      }
      setCreatedCompanyId(newCompanyId);

      // Step 2a: If any attached client owns yachts the rep didn't already
      // attach, prompt to pull them in. Resolved here so the rep can opt out
      // per-yacht rather than getting a blanket "everything attached" flow.
      try {
        const yachtsToOffer: { yachtId: string; yachtName: string }[] = [];
        for (const clientId of attachedClientIds) {
          const res = await apiFetch<{
            data: Array<{ id: string; name: string; currentOwnerType: string }>;
          }>(`/api/v1/yachts?ownerType=client&ownerId=${clientId}`);
          for (const y of res.data) {
            if (!attachedYachtIds.includes(y.id)) {
              yachtsToOffer.push({ yachtId: y.id, yachtName: y.name });
            }
          }
        }
        if (yachtsToOffer.length > 0) {
          setPendingYachtPullIn(yachtsToOffer);
          return;
        }
      } catch {
        // Yacht lookup failure is non-fatal - fall through to interest prompt.
      }

      // (Step 2b - yacht-owner pull-in - deferred. Adding it cleanly needs
      // the yachts API to surface prior owners post-transfer, which currently
      // only lives in the activity log. Tracked for follow-up.)

      finishWithInterestPrompt(newCompanyId);
    },
    onError: (err: unknown) => {
      const msg = err instanceof Error ? err.message : 'Failed to save company';
      setFormError(msg);
    },
  });

  function finishWithInterestPrompt(newCompanyId: string) {
    void newCompanyId;
    if (attachedClientIds.length > 0) {
      setCreateInterestFor(attachedClientIds[0] ?? null);
    } else {
      onOpenChange(false);
    }
  }

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent className="w-full sm:max-w-2xl lg:max-w-4xl overflow-y-auto">
        <SheetHeader>
          <SheetTitle>{isEdit ? 'Edit Company' : 'New Company'}</SheetTitle>
        </SheetHeader>

        <form
          onSubmit={scrollOnError((data) => {
            setFormError(null);
            mutation.mutate(data as CreateCompanyInput);
          })}
          className="space-y-6 py-6"
        >
          <FormErrorSummary errors={errors} />
          {/* Basics */}
          <div className="space-y-4">
            <h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
              Basics
            </h3>

            <div className="grid grid-cols-2 gap-4">
              <div className="col-span-2 space-y-1">
                <Label>Name *</Label>
                <Input {...register('name')} placeholder="Acme Holdings Ltd" />
                {errors.name && <p className="text-xs text-destructive">{errors.name.message}</p>}
              </div>

              <div className="col-span-2 space-y-1">
                <Label>Legal Name</Label>
                <Input {...register('legalName')} placeholder="Acme Holdings Limited" />
              </div>
            </div>
          </div>

          <Separator />

          {/* Registration */}
          <div className="space-y-4">
            <h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
              Registration
            </h3>
            <div className="grid grid-cols-2 gap-4">
              <div className="space-y-1">
                <Label>Tax ID</Label>
                <Input {...register('taxId')} placeholder="VAT / EIN" />
              </div>
              <div className="space-y-1">
                <Label>Registration Number</Label>
                <Input {...register('registrationNumber')} placeholder="Company #" />
              </div>
              <div className="space-y-1">
                <Label>Incorporation Country</Label>
                <CountryCombobox
                  value={watch('incorporationCountryIso')}
                  onChange={(iso) => {
                    setValue('incorporationCountryIso', iso ?? undefined);
                    // Wipe subdivision when country flips - codes are country-scoped.
                    setValue('incorporationSubdivisionIso', undefined);
                  }}
                  data-testid="company-incorp-country"
                />
              </div>
              <div className="space-y-1">
                <Label>Incorporation Region</Label>
                <SubdivisionCombobox
                  value={watch('incorporationSubdivisionIso')}
                  onChange={(code) => setValue('incorporationSubdivisionIso', code ?? undefined)}
                  country={(watch('incorporationCountryIso') as CountryCode | undefined) ?? null}
                  data-testid="company-incorp-subdivision"
                />
              </div>
              <div className="space-y-1">
                <Label>Incorporation Date</Label>
                <Controller
                  control={control}
                  name="incorporationDate"
                  render={({ field }) => {
                    // Schema coerces incorporationDate to a Date; the picker
                    // speaks YYYY-MM-DD. Bridge both directions so validation
                    // + downstream API payload stay unchanged.
                    const isoValue =
                      field.value instanceof Date
                        ? field.value.toISOString().split('T')[0]
                        : typeof field.value === 'string'
                          ? (field.value as string).split('T')[0]
                          : '';
                    return (
                      <DatePicker
                        value={isoValue}
                        onChange={(v) => field.onChange(v ? new Date(v) : undefined)}
                      />
                    );
                  }}
                />
                {errors.incorporationDate && (
                  <p className="text-xs text-destructive">{errors.incorporationDate.message}</p>
                )}
              </div>
            </div>
          </div>

          <Separator />

          {/* Contact & Status */}
          <div className="space-y-4">
            <h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
              Contact & Status
            </h3>
            <div className="grid grid-cols-2 gap-4">
              <div className="space-y-1">
                <Label>Billing Email</Label>
                <Input
                  type="email"
                  {...register('billingEmail')}
                  placeholder="billing@example.com"
                />
                {errors.billingEmail && (
                  <p className="text-xs text-destructive">{errors.billingEmail.message}</p>
                )}
              </div>
              <div className="space-y-1">
                <Label>Status</Label>
                <Select
                  value={status}
                  onValueChange={(v) => setValue('status', v as CompanyStatus)}
                >
                  <SelectTrigger>
                    <SelectValue placeholder="Select status" />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="active">Active</SelectItem>
                    <SelectItem value="dissolved">Dissolved</SelectItem>
                  </SelectContent>
                </Select>
              </div>
            </div>
          </div>

          <Separator />

          {/* Connections - only on create. Editing membership / yacht ownership
              from this form would race with the same actions on the detail
              tabs (and the audit trail of a "create + attach 5 clients in one
              flow" is much more readable than 6 separate create rows). */}
          {!isEdit && (
            <div className="space-y-4">
              <h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
                Connections
              </h3>
              <div className="space-y-3">
                <div className="space-y-1">
                  <Label className="text-xs">Member clients</Label>
                  <EntityMultiPicker
                    endpoint="/api/v1/clients/options"
                    labelKey="fullName"
                    placeholder="Add a client…"
                    selectedIds={attachedClientIds}
                    onChange={setAttachedClientIds}
                  />
                  <p className="text-xs text-muted-foreground">
                    Each pick becomes a company member with role=member. You can refine roles
                    afterwards on the Members tab.
                  </p>
                </div>
                <div className="space-y-1">
                  <Label className="text-xs">Yachts owned by the company</Label>
                  <EntityMultiPicker
                    endpoint="/api/v1/yachts/options"
                    labelKey="name"
                    placeholder="Add a yacht…"
                    selectedIds={attachedYachtIds}
                    onChange={setAttachedYachtIds}
                  />
                  <p className="text-xs text-muted-foreground">
                    Adding a yacht transfers its ownership to this company (logged in the
                    yacht&apos;s audit trail). Skip if you only want to associate without changing
                    ownership.
                  </p>
                </div>
                <div className="flex flex-wrap gap-2">
                  <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    onClick={() => setClientFormOpen(true)}
                  >
                    <Plus className="mr-1 h-3.5 w-3.5" aria-hidden /> New client
                  </Button>
                  <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    onClick={() => setYachtFormOpen(true)}
                  >
                    <Plus className="mr-1 h-3.5 w-3.5" aria-hidden /> New yacht
                  </Button>
                </div>
              </div>
            </div>
          )}

          <Separator />

          {/* Notes */}
          <div className="space-y-2">
            <Label>Notes</Label>
            <Textarea
              {...register('notes')}
              placeholder="Internal notes about this company"
              rows={4}
            />
          </div>

          <Separator />

          {/* Tags */}
          <div className="space-y-2">
            <Label>Tags</Label>
            <TagPicker selectedIds={tagIds} onChange={(ids) => setValue('tagIds', ids)} />
          </div>

          {formError && (
            <p className="text-sm text-destructive" role="alert">
              {formError}
            </p>
          )}

          <SheetFooter>
            <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
              Cancel
            </Button>
            <Button type="submit" disabled={isSubmitting || mutation.isPending}>
              {(isSubmitting || mutation.isPending) && (
                <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
              )}
              {isEdit ? 'Save changes' : 'Create Company'}
            </Button>
          </SheetFooter>
        </form>
      </SheetContent>

      {/* Stacked "+ New client" / "+ New yacht" forms. On successful create
          the picker we open them from doesn't know the new id yet - the
          ClientList / YachtList query refetches via react-query invalidation
          and the rep can pick the new entity from the dropdown immediately. */}
      <ClientForm open={clientFormOpen} onOpenChange={setClientFormOpen} />
      {yachtFormOpen && (
        <YachtForm
          open={yachtFormOpen}
          onOpenChange={setYachtFormOpen}
          // No initialOwner - the new yacht starts unowned-by-rules-engine; the
          // company-form will optionally transfer it on save.
        />
      )}

      <AlertDialog
        open={!!pendingYachtPullIn}
        onOpenChange={(o) => {
          if (!o && createdCompanyId) {
            setPendingYachtPullIn(null);
            finishWithInterestPrompt(createdCompanyId);
          }
        }}
      >
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Attach these yachts too?</AlertDialogTitle>
            <AlertDialogDescription>
              The clients you added own {pendingYachtPullIn?.length ?? 0}{' '}
              {pendingYachtPullIn?.length === 1 ? 'yacht' : 'yachts'} not yet linked to this
              company. Attaching transfers their ownership.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <div className="space-y-1 rounded-md border bg-muted/30 p-2 text-sm">
            {pendingYachtPullIn?.map((y) => (
              <div key={y.yachtId} className="flex items-center justify-between">
                <span className="truncate">{y.yachtName}</span>
              </div>
            ))}
          </div>
          <AlertDialogFooter>
            <AlertDialogCancel
              onClick={() => {
                setPendingYachtPullIn(null);
                if (createdCompanyId) finishWithInterestPrompt(createdCompanyId);
              }}
            >
              Skip
            </AlertDialogCancel>
            <AlertDialogAction
              onClick={async () => {
                if (!createdCompanyId || !pendingYachtPullIn) return;
                for (const y of pendingYachtPullIn) {
                  try {
                    await apiFetch(`/api/v1/yachts/${y.yachtId}/transfer`, {
                      method: 'POST',
                      body: {
                        newOwner: { type: 'company', id: createdCompanyId },
                        reason: 'Attached during company creation (yacht pull-in)',
                      },
                    });
                  } catch (err) {
                    toastError(err);
                  }
                }
                setPendingYachtPullIn(null);
                finishWithInterestPrompt(createdCompanyId);
              }}
            >
              Attach all
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      <AlertDialog
        open={!!createInterestFor && !pendingYachtPullIn}
        onOpenChange={(o) => {
          if (!o) {
            setCreateInterestFor(null);
            onOpenChange(false);
          }
        }}
      >
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Create an interest now?</AlertDialogTitle>
            <AlertDialogDescription>
              The new company is connected to {attachedClientIds.length}{' '}
              {attachedClientIds.length === 1 ? 'client' : 'clients'}. Want to open a new interest
              dialog pre-filled with one of them?
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel
              onClick={() => {
                setCreateInterestFor(null);
                onOpenChange(false);
              }}
            >
              Not now
            </AlertDialogCancel>
            <AlertDialogAction
              onClick={() => {
                // Close the company form, then open the interest form. The
                // interest form is rendered below via createInterestFor.
                onOpenChange(false);
              }}
            >
              Create interest
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      {/* Detached follow-up: interest form pre-filled with the first attached
          client. Stays mounted after this form closes so the rep can finish
          the new-interest flow uninterrupted. */}
      {createInterestFor && !open && (
        <InterestForm
          open={true}
          onOpenChange={(o) => {
            if (!o) {
              setCreateInterestFor(null);
              router.refresh();
            }
          }}
          defaultClientId={createInterestFor}
        />
      )}
    </Sheet>
  );
}

/**
 * Lightweight multi-pick combobox. Used by the company-form Connections
 * section for both clients and yachts since they share the same shape
 * (`{ value, label }` via useEntityOptions). Selected items render as
 * removable chips above the picker so the rep can see at a glance what
 * they're about to attach.
 */
function EntityMultiPicker({
  endpoint,
  labelKey,
  placeholder,
  selectedIds,
  onChange,
}: {
  endpoint: string;
  labelKey: string;
  placeholder: string;
  selectedIds: string[];
  onChange: (ids: string[]) => void;
}) {
  const [open, setOpen] = useState(false);
  const { options, setSearch } = useEntityOptions({ endpoint, labelKey });
  const labelById = useMemo(() => {
    const m = new Map<string, string>();
    for (const o of options) m.set(o.value, o.label);
    return m;
  }, [options]);

  function toggle(id: string) {
    if (selectedIds.includes(id)) {
      onChange(selectedIds.filter((x) => x !== id));
    } else {
      onChange([...selectedIds, id]);
    }
  }

  return (
    <div className="space-y-2">
      {selectedIds.length > 0 ? (
        <div className="flex flex-wrap gap-1.5">
          {selectedIds.map((id) => (
            <Badge key={id} variant="secondary" className="gap-1 pr-1">
              <span className="max-w-56 truncate">{labelById.get(id) ?? id.slice(0, 8)}</span>
              <button
                type="button"
                className="rounded-full p-0.5 hover:bg-muted-foreground/20"
                onClick={() => toggle(id)}
                aria-label="Remove"
              >
                <X className="h-3 w-3" />
              </button>
            </Badge>
          ))}
        </div>
      ) : null}
      <Popover open={open} onOpenChange={setOpen} modal>
        <PopoverTrigger asChild>
          <Button
            type="button"
            variant="outline"
            size="sm"
            role="combobox"
            aria-expanded={open}
            className={cn(
              'w-full justify-between font-normal',
              !selectedIds.length && 'text-muted-foreground',
            )}
          >
            {placeholder}
            <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" aria-hidden />
          </Button>
        </PopoverTrigger>
        <PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0" align="start">
          <Command shouldFilter={false}>
            <CommandInput placeholder="Search…" onValueChange={setSearch} />
            <CommandList>
              <CommandEmpty>No results.</CommandEmpty>
              <CommandGroup>
                {options.map((opt) => {
                  const isSelected = selectedIds.includes(opt.value);
                  return (
                    <CommandItem
                      key={opt.value}
                      value={opt.value}
                      onSelect={() => toggle(opt.value)}
                    >
                      <Check
                        className={cn('mr-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0')}
                      />
                      {opt.label}
                    </CommandItem>
                  );
                })}
              </CommandGroup>
            </CommandList>
          </Command>
        </PopoverContent>
      </Popover>
    </div>
  );
}
