/**
 * BusinessCardEditor - vendor business profile editor (FDS §5.7).
 *
 * Fields: business name, description, business types, hours via HoursEditor,
 * phone, website, logo (ImageUploadField), hero (ImageUploadField),
 * address (IsraeliAddressField).
 */

'use client';

import { useMemo, useState, useEffect, useRef } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import * as Sentry from '@sentry/astro';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { HydratedIsland } from '@/components/HydratedIsland';
import { qk } from '@/lib/query/keys';
import { notify } from '@/lib/query/toast-bridge';
import { useT } from '@/lib/i18n/react';
import { VendorShell } from '@/components/ui/layout/VendorShell';
import { PageShellSkeleton, SkeletonGuard } from '@/components/ui/feedback/Skeleton';
import { useLoadingOverlay } from '@/components/ui/feedback/LoadingOverlay';
import { HoursEditor } from '@/components/ui/domain/HoursEditor';
import { IsraeliAddressField } from '@/components/ui/domain/IsraeliAddressField';
import type { IsraeliAddressValue } from '@/components/ui/domain/IsraeliAddressField';
import { ImageUploadField } from '@/components/ui/primitives/ImageUploadField';
import { VendorHeroPreview } from '@/components/ui/domain/VendorHeroPreview';
import { Button } from '@/components/ui/primitives/Button';
import { Input } from '@/components/ui/primitives/Input';
import { Textarea } from '@/components/ui/primitives/Textarea';
import { FormField } from '@/components/ui/primitives/FormField';
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
} from '@/components/ui/primitives/Select';
import { FilterChip } from '@/components/ui/primitives/FilterChip';
import type { DayOfWeek, DayHours } from '@/components/ui/domain/HoursEditor';
import { captureCaught } from '@/lib/observability';
import { getCsrfToken } from '@/lib/csrf';

interface BCErrorMessages {
  businessNameRequired: string;
  businessNameTooLong: string;
  descriptionTooLong: string;
  phoneTooLong: string;
  websiteInvalid: string;
}

function buildSchema(msgs: BCErrorMessages) {
  return z.object({
    businessName: z
      .string()
      .trim()
      .min(1, { message: msgs.businessNameRequired })
      .max(120, { message: msgs.businessNameTooLong }),
    description: z.string().trim().max(2000, { message: msgs.descriptionTooLong }).optional(),
    phone: z.string().trim().max(20, { message: msgs.phoneTooLong }).optional(),
    website: z.url({ message: msgs.websiteInvalid }).or(z.literal('')).optional(),
    logoUrl: z.string().trim().optional(),
    heroImageUrl: z.string().trim().optional(),
  });
}

type FormValues = z.infer<ReturnType<typeof buildSchema>>;

const DEFAULT_HOURS: Record<DayOfWeek, DayHours> = {
  monday: { open: true, openTime: '09:00', closeTime: '22:00' },
  tuesday: { open: true, openTime: '09:00', closeTime: '22:00' },
  wednesday: { open: true, openTime: '09:00', closeTime: '22:00' },
  thursday: { open: true, openTime: '09:00', closeTime: '22:00' },
  friday: { open: true, openTime: '09:00', closeTime: '15:00' },
  saturday: { open: false, openTime: '09:00', closeTime: '22:00' },
  sunday: { open: true, openTime: '09:00', closeTime: '22:00' },
};

export interface BusinessCardEditorProps {
  defaultValues?: Partial<
    FormValues & {
      hours: Record<DayOfWeek, DayHours>;
      specialNotes: string;
      heroFocalX: number;
      heroFocalY: number;
      heroImageR2Key: string;
      heroPendingImageUrl: string | null;
      heroImageApprovalStatus: 'PENDING' | 'APPROVED' | 'REJECTED';
      heroImageRejectReasonCode: string | null;
      logoApprovalStatus?: 'PENDING' | 'APPROVED' | 'REJECTED';
      pendingLogoUrl?: string | null;
      logoRejectReasonCode?: string | null;
    }
  >;
}

interface ApiVendorProfile {
  businessName: string;
  description?: string;
  phone?: string | null;
  website?: string | null;
  businessTypeIds?: string[];
  logoUrl?: string | null;
  heroImageUrl?: string | null;
  heroFocalX?: number;
  heroFocalY?: number;
  heroImageR2Key?: string | null;
  heroPendingImageUrl?: string | null;
  heroImageApprovalStatus?: 'PENDING' | 'APPROVED' | 'REJECTED' | null;
  heroImageRejectReasonCode?: string | null;
  logoApprovalStatus?: 'PENDING' | 'APPROVED' | 'REJECTED' | null;
  pendingLogoUrl?: string | null;
  logoRejectReasonCode?: string | null;
  hours?: {
    mondayOpen?: string;
    mondayClose?: string;
    mondayClosed?: boolean;
    tuesdayOpen?: string;
    tuesdayClose?: string;
    tuesdayClosed?: boolean;
    wednesdayOpen?: string;
    wednesdayClose?: string;
    wednesdayClosed?: boolean;
    thursdayOpen?: string;
    thursdayClose?: string;
    thursdayClosed?: boolean;
    fridayOpen?: string;
    fridayClose?: string;
    fridayClosed?: boolean;
    saturdayOpen?: string;
    saturdayClose?: string;
    saturdayClosed?: boolean;
    sundayOpen?: string;
    sundayClose?: string;
    sundayClosed?: boolean;
    specialNotes?: string;
  };
}

interface BusinessTypeOption {
  id: string;
  nameHe: string;
  nameEn: string;
}

interface ApiAddress {
  id: string;
  vendorId: string;
  label: string;
  fullAddress: string;
  city: string;
  cityCode: string | null;
  streetCode: string | null;
  streetName: string | null;
  houseNumber: string | null;
  apt: string | null;
  lat: string | null;
  lng: string | null;
}

function mapApiHours(h: ApiVendorProfile['hours']): Record<DayOfWeek, DayHours> {
  if (!h) return DEFAULT_HOURS;
  const day = (open?: string, close?: string, closed?: boolean): DayHours => ({
    open: !closed,
    openTime: open ?? '09:00',
    closeTime: close ?? '22:00',
  });
  return {
    monday: day(h.mondayOpen, h.mondayClose, h.mondayClosed),
    tuesday: day(h.tuesdayOpen, h.tuesdayClose, h.tuesdayClosed),
    wednesday: day(h.wednesdayOpen, h.wednesdayClose, h.wednesdayClosed),
    thursday: day(h.thursdayOpen, h.thursdayClose, h.thursdayClosed),
    friday: day(h.fridayOpen, h.fridayClose, h.fridayClosed),
    saturday: day(h.saturdayOpen, h.saturdayClose, h.saturdayClosed),
    sunday: day(h.sundayOpen, h.sundayClose, h.sundayClosed),
  };
}

// ─── Mutation vars type ───────────────────────────────────────────────────────

interface SaveProfileVars {
  profile: FormValues & {
    logoUrl: string;
    heroImageUrl: string;
    heroFocalX: number;
    heroFocalY: number;
    heroImageR2Key?: string;
    hours: Record<DayOfWeek, DayHours>;
    specialNotes: string;
    businessTypeIds: string[];
  };
  address: IsraeliAddressValue | null;
}

// ─── Helpers: normalize form values for API boundaries ───────────────────────

/** Trim + return undefined for empty (so schema's z.url()/z.regex won't see ""). */
function nonEmpty(v: string | null | undefined): string | undefined {
  if (v == null) return undefined;
  const trimmed = v.trim();
  return trimmed === '' ? undefined : trimmed;
}

/** Strip everything that isn't a digit or a leading "+". */
function normalizePhone(v: string | null | undefined): string | undefined {
  const s = nonEmpty(v);
  if (s == null) return undefined;
  const cleaned = s.replace(/[^\d+]/g, '');
  // Collapse multiple +s to a single leading one
  const sign = cleaned.startsWith('+') ? '+' : '';
  const digits = cleaned.replace(/\+/g, '');
  return digits.length === 0 ? undefined : `${sign}${digits}`;
}

// dayOfWeek: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat
const DAY_INDEX: Array<{ key: DayOfWeek; idx: number }> = [
  { key: 'sunday', idx: 0 },
  { key: 'monday', idx: 1 },
  { key: 'tuesday', idx: 2 },
  { key: 'wednesday', idx: 3 },
  { key: 'thursday', idx: 4 },
  { key: 'friday', idx: 5 },
  { key: 'saturday', idx: 6 },
];

type AddressCompleteness = 'empty' | 'full' | 'partial';

function getAddressCompleteness(addr: IsraeliAddressValue | null): AddressCompleteness {
  if (!addr) return 'empty';
  const hasCity = Boolean(addr.cityName?.trim() || addr.cityCode?.trim());
  const hasStreet = Boolean(addr.streetName?.trim());
  const hasHouse = Boolean(addr.houseNumber?.trim());
  const filled = [hasCity, hasStreet, hasHouse].filter(Boolean).length;
  if (filled === 0) return 'empty';
  if (filled === 3) return 'full';
  return 'partial';
}

function toHoursPayload(hours: Record<DayOfWeek, DayHours>) {
  return DAY_INDEX.map(({ key, idx }) => {
    const day = hours[key];
    return {
      dayOfWeek: idx,
      openTime: day.open ? day.openTime : null,
      closeTime: day.open ? day.closeTime : null,
      isClosed: !day.open,
    };
  });
}

// ─── Save mutation ───────────────────────────────────────────────────────────

interface SaveErrorMessages {
  profile: string;
  hours: string;
  address: string;
  card: string;
}

async function saveBusinessCardMutationFn(
  { profile: p, address }: SaveProfileVars,
  errors: SaveErrorMessages,
): Promise<void> {
  const csrf = getCsrfToken();

  const profileBody: Record<string, unknown> = {
    businessName: p.businessName,
  };
  const description = nonEmpty(p.description);
  if (description !== undefined) profileBody.description = description;
  const phone = normalizePhone(p.phone);
  if (phone !== undefined) profileBody.phone = phone;
  if (p.website !== undefined && p.website !== null) {
    profileBody.website = p.website.trim();
  }
  const logoUrl = nonEmpty(p.logoUrl);
  if (logoUrl !== undefined) profileBody.logoUrl = logoUrl;
  const heroImageUrl = nonEmpty(p.heroImageUrl);
  if (heroImageUrl !== undefined) profileBody.heroImageUrl = heroImageUrl;
  if (p.heroImageR2Key) profileBody.heroImageR2Key = p.heroImageR2Key;
  profileBody.heroFocalX = p.heroFocalX;
  profileBody.heroFocalY = p.heroFocalY;
  profileBody.businessTypeIds = p.businessTypeIds;

  const profilePromise = fetch('/api/vendor/profile', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
    body: JSON.stringify(profileBody),
  }).then((r) => r.json() as Promise<{ ok: boolean; error?: string }>);

  const hoursPromise = fetch('/api/vendor/business-hours', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
    body: JSON.stringify({
      hours: toHoursPayload(p.hours),
      specialNotes: p.specialNotes ?? '',
    }),
  }).then((r) => r.json() as Promise<{ ok: boolean; error?: string }>);

  const addressPromise =
    address?.cityName && address.streetName && address.houseNumber
      ? fetch('/api/vendor/address', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
          body: JSON.stringify(address),
        }).then((r) => r.json() as Promise<{ ok: boolean; error?: string }>)
      : Promise.resolve({ ok: true as const });

  const [profileData, hoursData, addressData] = await Promise.all([
    profilePromise,
    hoursPromise,
    addressPromise,
  ]);
  if (!profileData.ok) throw new Error(profileData.error ?? errors.profile);
  if (!(hoursData as { ok: boolean }).ok)
    throw new Error((hoursData as { error?: string }).error ?? errors.hours);
  if (!(addressData as { ok: boolean }).ok)
    throw new Error((addressData as { error?: string }).error ?? errors.address);
}

function useSaveBusinessCard(errors: SaveErrorMessages) {
  const qc = useQueryClient();
  return useMutation<void, Error, SaveProfileVars, { prev: Record<string, unknown> | undefined }>({
    mutationFn: (vars) => saveBusinessCardMutationFn(vars, errors),
    onMutate: async () => {
      await qc.cancelQueries({ queryKey: qk.vendorProfile() });
      const prev = qc.getQueryData<Record<string, unknown>>(qk.vendorProfile());
      qc.setQueryData(qk.vendorProfile(), (old) => old ?? {});
      return { prev };
    },
    onError: (err, _vars, ctx) => {
      if (ctx?.prev !== undefined) {
        qc.setQueryData(qk.vendorProfile(), ctx.prev);
      }
      Sentry.addBreadcrumb({
        category: 'mutation',
        level: 'warning',
        message: 'optimistic rollback',
        data: { queryKey: qk.vendorProfile() },
      });
      const msg = err instanceof Error && err.message ? err.message : errors.card;
      notify.error(msg);
    },
    onSettled: () => {
      void qc.invalidateQueries({ queryKey: qk.vendorProfile() });
    },
  });
}

// ─── Component ────────────────────────────────────────────────────────────────

function BusinessCardEditorInner({ defaultValues }: BusinessCardEditorProps) {
  const t = useT('vendor_business_card');
  const tCommon = useT('common');
  const tUpload = useT('image_upload');
  const tProfile = useT('vendor_profile');
  const heroUploadRef = useRef<HTMLDivElement>(null);
  const logoUploadRef = useRef<HTMLDivElement>(null);
  const saveErrors = useMemo(
    () => ({
      profile: t('error_save_profile'),
      hours: t('error_save_hours'),
      address: t('error_save_address'),
      card: t('error_save_card'),
    }),
    [t],
  );
  const schema = useMemo(
    () =>
      buildSchema({
        businessNameRequired: t('error_business_name_required'),
        businessNameTooLong: t('error_business_name_too_long'),
        descriptionTooLong: t('error_description_too_long'),
        phoneTooLong: t('error_phone_too_long'),
        websiteInvalid: t('error_website_invalid'),
      }),
    [t],
  );

  const [hours, setHours] = useState<Record<DayOfWeek, DayHours>>(
    defaultValues?.hours ?? DEFAULT_HOURS,
  );
  const [specialNotes, setSpecialNotes] = useState(defaultValues?.specialNotes ?? '');
  const [address, setAddress] = useState<IsraeliAddressValue | null>(null);
  const [addressError, setAddressError] = useState<string | null>(null);
  const [logoUrl, setLogoUrl] = useState<string>(defaultValues?.logoUrl ?? '');
  const [heroImageUrl, setHeroImageUrl] = useState<string>(defaultValues?.heroImageUrl ?? '');
  const [heroFocalX, setHeroFocalX] = useState<number>(defaultValues?.heroFocalX ?? 0.5);
  const [heroFocalY, setHeroFocalY] = useState<number>(defaultValues?.heroFocalY ?? 0.5);
  const [heroImageR2Key, setHeroImageR2Key] = useState<string>(defaultValues?.heroImageR2Key ?? '');
  const [heroPendingImageUrl, setHeroPendingImageUrl] = useState<string | null>(
    defaultValues?.heroPendingImageUrl ?? null,
  );
  const [heroImageApprovalStatus, setHeroImageApprovalStatus] = useState<
    'PENDING' | 'APPROVED' | 'REJECTED' | undefined
  >(defaultValues?.heroImageApprovalStatus ?? undefined);
  const [heroImageRejectReasonCode, setHeroImageRejectReasonCode] = useState<string | null>(
    defaultValues?.heroImageRejectReasonCode ?? null,
  );
  const [logoApprovalStatus, setLogoApprovalStatus] = useState<
    'PENDING' | 'APPROVED' | 'REJECTED' | undefined
  >(defaultValues?.logoApprovalStatus ?? undefined);
  const [pendingLogoUrl, setPendingLogoUrl] = useState<string | null>(
    defaultValues?.pendingLogoUrl ?? null,
  );
  const [logoRejectReasonCode, setLogoRejectReasonCode] = useState<string | null>(
    defaultValues?.logoRejectReasonCode ?? null,
  );
  const [isLoadingProfile, setIsLoadingProfile] = useState(!defaultValues);
  const [businessTypes, setBusinessTypes] = useState<BusinessTypeOption[]>([]);
  const [businessTypeIds, setBusinessTypeIds] = useState<string[]>([]);
  const saveCard = useSaveBusinessCard(saveErrors);
  const { isSuccess: saveSucceeded, reset: resetSaveCard } = saveCard;
  const { show: showOverlay, hide: hideOverlay } = useLoadingOverlay();

  const {
    register,
    handleSubmit,
    reset,
    control,
    formState: { errors, isSubmitting, isDirty },
  } = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: {
      businessName: defaultValues?.businessName ?? '',
      description: defaultValues?.description ?? '',
      phone: defaultValues?.phone ?? '',
      website: defaultValues?.website ?? '',
      logoUrl: defaultValues?.logoUrl ?? '',
      heroImageUrl: defaultValues?.heroImageUrl ?? '',
    },
  });

  const businessName = useWatch({ control, name: 'businessName' });
  const descriptionValue = useWatch({ control, name: 'description' }) ?? '';
  const descriptionLength = descriptionValue.length;

  useEffect(() => {
    if (isDirty && saveSucceeded) {
      resetSaveCard();
    }
  }, [isDirty, saveSucceeded, resetSaveCard]);

  const rejectReasons = tUpload('reject_reason') as unknown as Record<string, string>;

  function resolveRejectReason(code: string | null): string | null {
    if (!code) return null;
    return rejectReasons[code] ?? tUpload('reject_reason_fallback');
  }

  // Always load business types for the select field
  useEffect(() => {
    let cancelled = false;
    fetch('/api/business-types')
      .then((r) => r.json() as Promise<{ ok: boolean; businessTypes?: BusinessTypeOption[] }>)
      .then(({ ok, businessTypes: bts }) => {
        if (!ok || cancelled) return;
        setBusinessTypes(bts ?? []);
      })
      .catch((err) => {
        captureCaught(err, {
          scope: 'features.vendor-business-card.BusinessCardEditor',
          severity: 'info',
        });
      });
    return () => {
      cancelled = true;
    };
  }, [defaultValues, reset]);

  // Load existing profile + address on mount when no defaultValues passed from server
  useEffect(() => {
    if (defaultValues) return;
    let cancelled = false;

    const profilePromise = fetch('/api/vendor/profile')
      .then((r) => r.json() as Promise<{ ok: boolean; vendor: ApiVendorProfile }>)
      .then(({ ok, vendor }) => {
        if (!ok || cancelled) return;
        reset({
          businessName: vendor.businessName ?? '',
          description: vendor.description ?? '',
          phone: vendor.phone ?? '',
          website: vendor.website ?? '',
        });
        setBusinessTypeIds(vendor.businessTypeIds ?? []);
        setLogoUrl(vendor.logoUrl ?? '');
        setHeroImageUrl(vendor.heroImageUrl ?? '');
        setHeroFocalX(vendor.heroFocalX ?? 0.5);
        setHeroFocalY(vendor.heroFocalY ?? 0.5);
        setHeroImageR2Key(vendor.heroImageR2Key ?? '');
        setHeroPendingImageUrl(vendor.heroPendingImageUrl ?? null);
        setHeroImageApprovalStatus(vendor.heroImageApprovalStatus ?? undefined);
        setHeroImageRejectReasonCode(vendor.heroImageRejectReasonCode ?? null);
        setLogoApprovalStatus(vendor.logoApprovalStatus ?? undefined);
        setPendingLogoUrl(vendor.pendingLogoUrl ?? null);
        setLogoRejectReasonCode(vendor.logoRejectReasonCode ?? null);
        if (vendor.hours) {
          setHours(mapApiHours(vendor.hours));
          setSpecialNotes(vendor.hours.specialNotes ?? '');
        }
      })
      .catch((err) => {
        captureCaught(err, {
          scope: 'features.vendor-business-card.BusinessCardEditor',
          severity: 'info',
        });
        /* swallow — fallback to empty list */
      });

    const addressPromise = fetch('/api/vendor/address')
      .then((r) => r.json() as Promise<{ ok: boolean; address: ApiAddress | null }>)
      .then(({ ok, address: addr }) => {
        if (!ok || cancelled || !addr) return;
        // Reconstruct IsraeliAddressValue from stored address
        setAddress({
          cityCode: addr.cityCode ?? '',
          cityName: addr.city,
          streetCode: addr.streetCode ?? '',
          streetName: addr.streetName ?? '',
          houseNumber: addr.houseNumber ?? '',
          apt: addr.apt ?? '',
          lat: addr.lat ? parseFloat(addr.lat) : null,
          lng: addr.lng ? parseFloat(addr.lng) : null,
        });
      })
      .catch((err) => {
        captureCaught(err, {
          scope: 'features.vendor-business-card.BusinessCardEditor',
          severity: 'info',
        });
        /* swallow — fallback to empty list */
      });

    Promise.all([profilePromise, addressPromise]).finally(() => {
      if (!cancelled) setIsLoadingProfile(false);
    });

    return () => {
      cancelled = true;
    };
  }, [defaultValues, reset]);

  async function onSubmit(values: FormValues) {
    const addressStatus = getAddressCompleteness(address);
    if (addressStatus === 'partial') {
      setAddressError(t('error_address_incomplete'));
      return;
    }
    setAddressError(null);

    showOverlay(tCommon('saving'));
    try {
      await saveCard.mutateAsync({
        profile: {
          ...values,
          businessTypeIds,
          logoUrl,
          heroImageUrl,
          heroFocalX,
          heroFocalY,
          ...(heroImageR2Key ? { heroImageR2Key } : {}),
          hours,
          specialNotes,
        },
        address: addressStatus === 'full' ? address : null,
      });
      reset(values);
    } finally {
      hideOverlay();
    }
  }

  if (isLoadingProfile) {
    return (
      <VendorShell variant="dashboard" currentPath="/vendor/profile">
        <div aria-busy="true" role="status">
          <span className="sr-only">{tCommon('loading')}</span>
          <SkeletonGuard delay={0}>
            <PageShellSkeleton />
          </SkeletonGuard>
        </div>
      </VendorShell>
    );
  }

  return (
    <VendorShell variant="dashboard" currentPath="/vendor/profile">
      <div className="px-4 py-4 lg:mx-auto lg:max-w-2xl lg:p-6">
        <h1 className="text-text-primary mb-6 text-2xl font-bold">{t('editHeading')}</h1>

        <form onSubmit={handleSubmit(onSubmit)} noValidate className="flex flex-col gap-4">
          {/* Business name */}
          <FormField
            htmlFor="bce-name"
            label={t('business_name')}
            error={errors.businessName?.message}
          >
            <Input
              id="bce-name"
              type="text"
              invalid={!!errors.businessName}
              {...register('businessName')}
            />
          </FormField>

          {/* Description */}
          <FormField
            htmlFor="bce-desc"
            label={t('description')}
            hint={t('description_hint')}
            error={errors.description?.message}
          >
            <Textarea
              id="bce-desc"
              rows={4}
              invalid={!!errors.description}
              maxLength={2000}
              {...register('description')}
            />
            <p className="text-text-muted text-end text-xs tabular-nums">
              {t('description_char_count')
                .replace('{current}', String(descriptionLength))
                .replace('{max}', '2000')}
            </p>
          </FormField>

          {/* Phone */}
          <FormField htmlFor="bce-phone" label={t('phone')} hint={t('phone_hint')}>
            <Input id="bce-phone" type="tel" dir="ltr" autoComplete="tel" {...register('phone')} />
          </FormField>

          {/* Website */}
          <FormField htmlFor="bce-website" label={t('website')} error={errors.website?.message}>
            <Input
              id="bce-website"
              type="url"
              dir="ltr"
              invalid={!!errors.website}
              {...register('website')}
            />
          </FormField>

          {/* Business types — multi-select (0..5) */}
          <FormField htmlFor="bce-business-type" label={t('business_type')}>
            <div className="flex flex-col gap-2">
              <Select
                value=""
                onValueChange={(id) => {
                  if (!id || businessTypeIds.includes(id) || businessTypeIds.length >= 5) return;
                  setBusinessTypeIds([...businessTypeIds, id]);
                }}
              >
                <SelectTrigger id="bce-business-type" disabled={businessTypeIds.length >= 5}>
                  <SelectValue placeholder={t('business_type_placeholder')} />
                </SelectTrigger>
                <SelectContent>
                  {businessTypes
                    .filter((bt) => !businessTypeIds.includes(bt.id))
                    .map((bt) => (
                      <SelectItem key={bt.id} value={bt.id}>
                        {bt.nameHe} / {bt.nameEn}
                      </SelectItem>
                    ))}
                </SelectContent>
              </Select>
              {businessTypeIds.length < 5 && (
                <p className="text-text-muted text-xs">{t('business_type_limit_hint')}</p>
              )}
              {businessTypeIds.length === 5 && (
                <p className="text-text-muted text-xs">{t('business_type_limit_reached')}</p>
              )}
              {businessTypeIds.length > 0 && (
                <div className="flex flex-wrap gap-1">
                  {businessTypeIds.map((id) => {
                    const bt = businessTypes.find((x) => x.id === id);
                    const typeLabel = bt ? `${bt.nameHe} / ${bt.nameEn}` : id;
                    return (
                      <FilterChip
                        key={id}
                        pressed
                        aria-label={t('business_type_remove_aria').replace('{type}', typeLabel)}
                        onClick={() => setBusinessTypeIds(businessTypeIds.filter((x) => x !== id))}
                      >
                        {typeLabel} ×
                      </FilterChip>
                    );
                  })}
                </div>
              )}
            </div>
          </FormField>

          {/* Logo upload */}
          <div className="flex flex-col gap-1">
            <div ref={logoUploadRef} id="bce-logo-upload">
              <ImageUploadField
                label={t('logo')}
                value={(pendingLogoUrl ?? logoUrl) || null}
                onChange={(url) => {
                  setLogoUrl(url);
                  setPendingLogoUrl(null);
                  setLogoApprovalStatus('PENDING');
                  setLogoRejectReasonCode(null);
                }}
                purpose="vendor_logo"
                aspectRatio="1:1"
              />
            </div>
            <p className="text-text-muted text-xs">{t('logo_hint')}</p>
          </div>

          {logoApprovalStatus === 'REJECTED' && (
            <div
              role="alert"
              className="rounded-lg border border-[var(--color-error)] bg-[color-mix(in_srgb,var(--color-error)_10%,transparent)] p-4 text-sm"
            >
              <p className="mb-1 font-semibold text-[var(--color-error)]">
                {tProfile('rejection_banner_title_logo')}
              </p>
              <p className="text-[var(--color-error)]">
                {resolveRejectReason(logoRejectReasonCode)}
              </p>
              <Button
                type="button"
                variant="secondary"
                size="sm"
                className="mt-3"
                onClick={() => {
                  logoUploadRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
                  logoUploadRef.current?.querySelector<HTMLElement>('button')?.focus();
                }}
              >
                {tUpload('rejection_reupload_cta')}
              </Button>
            </div>
          )}
          {logoApprovalStatus === 'PENDING' && (
            <div
              role="status"
              className="rounded-lg border border-[var(--color-warning)] bg-[color-mix(in_srgb,var(--color-warning)_10%,transparent)] p-3 text-sm text-[var(--color-text)]"
            >
              <p className="font-semibold">{tUpload('pending_banner_title')}</p>
              <p className="mt-1">{tUpload('pending_banner_body')}</p>
            </div>
          )}

          {/* Hero image upload */}
          <div ref={heroUploadRef} id="bce-hero-upload">
            <ImageUploadField
              label={t('hero')}
              labelTooltip={t('hero_tooltip')}
              value={heroPendingImageUrl ?? heroImageUrl ?? null}
              onChange={setHeroImageUrl}
              onR2Key={(key) => {
                setHeroImageR2Key(key);
                setHeroPendingImageUrl(null);
                setHeroImageApprovalStatus('PENDING');
                setHeroImageRejectReasonCode(null);
              }}
              purpose="vendor_hero"
              aspectRatio="16:9"
              focal={{ x: heroFocalX, y: heroFocalY }}
              onFocalChange={(x, y) => {
                setHeroFocalX(x);
                setHeroFocalY(y);
              }}
            />
          </div>

          {heroImageApprovalStatus === 'REJECTED' && (
            <div
              role="alert"
              className="rounded-lg border border-[var(--color-error)] bg-[color-mix(in_srgb,var(--color-error)_10%,transparent)] p-4 text-sm"
            >
              <p className="mb-1 font-semibold text-[var(--color-error)]">
                {tProfile('rejection_banner_title_hero')}
              </p>
              <p className="text-[var(--color-error)]">
                {resolveRejectReason(heroImageRejectReasonCode)}
              </p>
              <Button
                type="button"
                variant="secondary"
                size="sm"
                className="mt-3"
                onClick={() => {
                  heroUploadRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
                  heroUploadRef.current?.querySelector<HTMLElement>('button')?.focus();
                }}
              >
                {tUpload('rejection_reupload_cta')}
              </Button>
            </div>
          )}
          {heroImageApprovalStatus === 'PENDING' && (
            <div
              role="status"
              className="rounded-lg border border-[var(--color-warning)] bg-[color-mix(in_srgb,var(--color-warning)_10%,transparent)] p-3 text-sm text-[var(--color-text)]"
            >
              <p className="font-semibold">{tUpload('pending_banner_title')}</p>
              <p className="mt-1">{tUpload('pending_banner_body')}</p>
            </div>
          )}

          <section aria-labelledby="hero-preview-heading">
            <span
              id="hero-preview-heading"
              className="text-text-primary mb-2 block text-sm font-semibold"
            >
              {t('hero_preview_heading')}
            </span>
            <VendorHeroPreview
              heroImageUrl={heroImageUrl || null}
              logoUrl={logoUrl || null}
              vendorName={businessName || ''}
              focalX={heroFocalX}
              focalY={heroFocalY}
            />
          </section>

          {/* Hours editor */}
          <section aria-labelledby="hours-heading">
            <span id="hours-heading" className="mb-2 block text-sm font-semibold">
              {t('hours')}
            </span>
            <HoursEditor
              value={hours}
              onChange={setHours}
              specialNotes={specialNotes}
              onNotesChange={setSpecialNotes}
            />
          </section>

          {/* Address */}
          <section aria-labelledby="address-heading" className="flex flex-col gap-1">
            <span
              id="address-heading"
              className="text-text-primary mb-1 block text-sm font-semibold"
            >
              {t('address_section')}
            </span>
            <IsraeliAddressField
              value={address}
              onChange={(next) => {
                setAddress(next);
                if (addressError) setAddressError(null);
              }}
            />
            {addressError && (
              <p role="alert" className="text-feedback-error text-sm">
                {addressError}
              </p>
            )}
          </section>

          {/* Success */}
          {saveSucceeded && (
            <p role="status" className="text-success-700 text-sm">
              {t('saved')}
            </p>
          )}

          <Button
            type="submit"
            variant="primary"
            size="lg"
            loading={isSubmitting || saveCard.isPending}
            className="w-full"
          >
            {t('save')}
          </Button>
        </form>
      </div>
    </VendorShell>
  );
}

export function BusinessCardEditor(props: BusinessCardEditorProps) {
  return (
    <HydratedIsland>
      <BusinessCardEditorInner {...props} />
    </HydratedIsland>
  );
}
