import { useState, useEffect, useRef } from 'react';
import Layout from '../components/Layout';
import LoadingButton from '../components/LoadingButton';
import SortTh from '../components/SortTh';
import { useSortable } from '../hooks/useSortable';
import { supabase } from '../lib/supabase';
import { generateBrandBookEditorPDF } from '../lib/brandBookEditor';
import { cleanupBrandBookStorage } from '../lib/deleteHelpers';
import { popupOverlayStyle, popupSurfaceStyle } from '../lib/popupStyles';

const BUCKET = 'brand-books';

const EMPTY_SIGN = () => ({
  _key: Math.random().toString(36).slice(2),
  signNumber: '',
  type: '',
  recommendation: '',
  specifications: '',
  notes: '',
  photo: null,
  photoPath: '',
  _photoPreview: '',
  existingPhoto: null,
  existingPhotoPath: '',
  _existingPhotoPreview: '',
  recommendationPhoto: null,
  recommendationPhotoPath: '',
  _recommendationPhotoPreview: '',
  signDetailPhoto: null,
  signDetailPhotoPath: '',
  _signDetailPhotoPreview: '',
});

const EMPTY_BOOK_INFO = {
  clientId: '',
  clientName: '',
  projectName: '',
  siteAddress: '',
  bookDate: new Date().toISOString().split('T')[0],
  preparedBy: '',
  revision: '01',
  template: 'fourge',
  // Cover page
  creationDate: new Date().toISOString().slice(0, 10),
  revisionDate: '',
  customerName: '',
  customerAddress: '',
  clientLogoUrl: '',
  clientContactName: '',
  clientContactEmail: '',
  clientContactPhone: '',
  approvedDate: '',
  approvalNotes: '',
};

const TEMPLATE_OPTIONS = [
  { value: 'fourge', label: 'Fourge Branding Template' },
  { value: 'bolchoz', label: 'Bolchoz Sign Solutions' },
];

const PHOTO_FILE_ACCEPT = 'image/*,.heic,.heif,.avif,.tif,.tiff,.bmp,.webp,.jpeg,.jpg,.png,.gif';
const PHOTO_FILE_EXTENSIONS = new Set(['heic', 'heif', 'avif', 'tif', 'tiff', 'bmp', 'webp', 'jpeg', 'jpg', 'png', 'gif']);
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN || '';

const normalizeRevision = (value) => {
  const digits = String(value ?? '').replace(/\D/g, '').slice(0, 2);
  if (!digits) return '00';
  return digits.padStart(2, '0');
};

const isPhotoFile = (file) => {
  if (!file) return false;
  if (file.type?.startsWith('image/')) return true;
  const extension = file.name?.split('.').pop()?.toLowerCase();
  return extension ? PHOTO_FILE_EXTENSIONS.has(extension) : false;
};

const getMapboxCoordinates = async (address) => {
  const features = await getMapboxAddressSuggestions(address, 1);
  const coordinates = features[0]?.center;
  if (!coordinates || coordinates.length < 2) throw new Error('No map result found for that address.');
  return coordinates;
};

const getMapboxAddressSuggestions = async (address, limit = 5) => {
  const params = new URLSearchParams({
    access_token: MAPBOX_TOKEN,
    autocomplete: 'true',
    country: 'us',
    limit: String(limit),
    types: 'address,place,postcode,locality,neighborhood,poi',
  });
  const response = await fetch(`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(address)}.json?${params.toString()}`);
  if (!response.ok) throw new Error('Unable to find that address.');
  const data = await response.json();
  return data.features || [];
};

const buildStaticMapUrl = ([lng, lat], style = 'satellite-streets-v12', zoom = 18) => {
  if (!MAPBOX_TOKEN || lng == null || lat == null) return '';
  const marker = `pin-s+f5a523(${lng},${lat})`;
  const camera = `${lng},${lat},${zoom},0`;
  return `https://api.mapbox.com/styles/v1/mapbox/${style}/static/${marker}/${camera}/700x467@2x?access_token=${encodeURIComponent(MAPBOX_TOKEN)}`;
};

// ─── Helpers ──────────────────────────────────────────────────────────────────
const getSignedUrl = async (path) => {
  if (!path) return null;
  const { data } = await supabase.storage.from(BUCKET).createSignedUrl(path, 86400 * 7);
  return data?.signedUrl || null;
};

const uploadFile = async (file, path) => {
  const { error } = await supabase.storage.from(BUCKET).upload(path, file, { upsert: true });
  if (error) throw error;
  return path;
};

// ─── Main Component ───────────────────────────────────────────────────────────
export default function BrandBook() {
  const [view, setView] = useState('list');
  const [savedBooks, setSavedBooks] = useState([]);
  const [loadingBooks, setLoadingBooks] = useState(true);
  const [currentId, setCurrentId] = useState(null);

  const [clients, setClients] = useState([]);
  const [bookInfo, setBookInfo] = useState(EMPTY_BOOK_INFO);
  const [siteMapFile, setSiteMapFile] = useState(null);
  const [siteMapPath, setSiteMapPath] = useState('');
  const [siteMapPreview, setSiteMapPreview] = useState(null);
  const [autoMapAddress, setAutoMapAddress] = useState('');
  const [autoMapLoading, setAutoMapLoading] = useState(false);
  const [autoMapError, setAutoMapError] = useState('');
  const [siteMapManuallyCleared, setSiteMapManuallyCleared] = useState(false);
  const [inventoryMapManuallyCleared, setInventoryMapManuallyCleared] = useState(false);
  const [addressSuggestions, setAddressSuggestions] = useState([]);
  const [addressSuggesting, setAddressSuggesting] = useState(false);
  const [showAddressSuggestions, setShowAddressSuggestions] = useState(false);
  const [signs, setSigns] = useState([EMPTY_SIGN()]);
  const [photoItems, setPhotoItems] = useState([]);

  const [inventoryMapFile, setInventoryMapFile] = useState(null);
  const [inventoryMapPath, setInventoryMapPath] = useState('');
  const [inventoryMapPreview, setInventoryMapPreview] = useState(null);

  const [generating, setGenerating] = useState(false);
  const [saving, setSaving] = useState(false);
  const [notification, setNotification] = useState(null);
  const [projectLogoFile, setProjectLogoFile] = useState(null);
  const [projectLogoPath, setProjectLogoPath] = useState('');
  const [projectLogoPreview, setProjectLogoPreview] = useState('');
  const [uploadingClientLogo, setUploadingClientLogo] = useState(false);
  const [savingClientInfo, setSavingClientInfo] = useState(false);
  const [clientInfoSaved, setClientInfoSaved] = useState(false);

  const siteMapRef = useRef();
  const inventoryMapRef = useRef();
  const sitePhotosRef = useRef();
  const projectLogoRef = useRef();
  const clientLogoRef = useRef();
  const [filterCompany, setFilterCompany] = useState('');
  const [selectedBookIds, setSelectedBookIds] = useState([]);
  const { sortKey: bbSortKey, sortDir: bbSortDir, toggle: bbToggle, sort: bbSort } = useSortable('updated_at');

  useEffect(() => {
    supabase.from('companies').select('id, name').order('name').then(({ data }) => setClients(data || []));
    fetchBooks();
  }, []);

  useEffect(() => {
    const address = bookInfo.customerAddress.trim();
    if (!address || autoMapAddress === address) return undefined;
    if ((siteMapManuallyCleared || siteMapPath || (siteMapFile && !autoMapAddress))
      && (inventoryMapManuallyCleared || inventoryMapPath || (inventoryMapFile && !autoMapAddress))) return undefined;

    const timeoutId = setTimeout(() => {
      if (address.includes(',') && address.length >= 12) {
        loadMapsFromAddress(address, { silent: true });
      }
    }, 900);

    return () => clearTimeout(timeoutId);
  }, [
    bookInfo.customerAddress,
    siteMapPath,
    siteMapFile,
    siteMapManuallyCleared,
    inventoryMapPath,
    inventoryMapFile,
    inventoryMapManuallyCleared,
    autoMapAddress,
  ]);

  useEffect(() => {
    const query = bookInfo.customerAddress.trim();
    if (!MAPBOX_TOKEN || query.length < 3) {
      setAddressSuggestions([]);
      setAddressSuggesting(false);
      return undefined;
    }

    let cancelled = false;
    const timeoutId = setTimeout(async () => {
      setAddressSuggesting(true);
      try {
        const suggestions = await getMapboxAddressSuggestions(query, 5);
        if (!cancelled) setAddressSuggestions(suggestions);
      } catch {
        if (!cancelled) setAddressSuggestions([]);
      } finally {
        if (!cancelled) setAddressSuggesting(false);
      }
    }, 250);

    return () => {
      cancelled = true;
      clearTimeout(timeoutId);
    };
  }, [bookInfo.customerAddress]);

  const fetchBooks = async () => {
    setLoadingBooks(true);
    const { data } = await supabase.from('brand_books').select('*').order('updated_at', { ascending: false });
    setSavedBooks(data || []);
    setSelectedBookIds(prev => prev.filter(id => (data || []).some(book => book.id === id)));
    setLoadingBooks(false);
  };

  const set = (field) => (e) => setBookInfo(b => ({ ...b, [field]: e.target.value }));
  const handleRevisionChange = (e) => {
    const digits = e.target.value.replace(/\D/g, '').slice(0, 2);
    setBookInfo(b => ({ ...b, revision: digits }));
  };
  const handleRevisionBlur = () => {
    setBookInfo(b => ({ ...b, revision: normalizeRevision(b.revision) }));
  };

  const loadMapsFromAddress = async (address = bookInfo.customerAddress, { silent = false, feature = null } = {}) => {
    const trimmed = String(address || '').trim();
    if (!trimmed) {
      if (!silent) setAutoMapError('Enter a customer address first.');
      return;
    }
    if (!MAPBOX_TOKEN) {
      if (!silent) setAutoMapError('Mapbox token is not configured yet. Add VITE_MAPBOX_TOKEN in Vercel to enable automatic site maps.');
      return;
    }
    if (autoMapLoading) return;

    setAutoMapLoading(true);
    setAutoMapError('');
    try {
      const coordinates = feature?.center || await getMapboxCoordinates(trimmed);
      const shouldLoadSiteMap = !siteMapManuallyCleared || !siteMapFile;
      const shouldLoadInventoryMap = !inventoryMapManuallyCleared || !inventoryMapFile;
      const [siteResponse, inventoryResponse] = await Promise.all([
        shouldLoadSiteMap ? fetch(buildStaticMapUrl(coordinates, 'satellite-streets-v12')) : Promise.resolve(null),
        shouldLoadInventoryMap ? fetch(buildStaticMapUrl(coordinates, 'streets-v12', 18.2)) : Promise.resolve(null),
      ]);
      if ((siteResponse && !siteResponse.ok) || (inventoryResponse && !inventoryResponse.ok)) throw new Error('Unable to load map image.');
      const [siteBlob, inventoryBlob] = await Promise.all([
        siteResponse ? siteResponse.blob() : Promise.resolve(null),
        inventoryResponse ? inventoryResponse.blob() : Promise.resolve(null),
      ]);
      if ((siteBlob && !siteBlob.type.startsWith('image/')) || (inventoryBlob && !inventoryBlob.type.startsWith('image/'))) throw new Error('Map service did not return an image.');

      const fullAddress = feature?.place_name || trimmed;

      setBookInfo(b => ({ ...b, customerAddress: fullAddress, siteAddress: fullAddress }));
      if (siteBlob) {
        const siteFile = new File([siteBlob], 'site-map.png', { type: siteBlob.type || 'image/png' });
        setSiteMapFile(siteFile);
        setSiteMapPath('');
        setSiteMapPreview(URL.createObjectURL(siteFile));
        setSiteMapManuallyCleared(false);
      }
      if (inventoryBlob) {
        const inventoryFile = new File([inventoryBlob], 'inventory-map.png', { type: inventoryBlob.type || 'image/png' });
        setInventoryMapFile(inventoryFile);
        setInventoryMapPath('');
        setInventoryMapPreview(URL.createObjectURL(inventoryFile));
        setInventoryMapManuallyCleared(false);
      }
      setAutoMapAddress(fullAddress);
      setShowAddressSuggestions(false);
    } catch (error) {
      setAutoMapError(error.message || 'Unable to load map for this address.');
    } finally {
      setAutoMapLoading(false);
    }
  };

  const handleCustomerAddressChange = (e) => {
    const value = e.target.value;
    setBookInfo(b => ({ ...b, customerAddress: value, siteAddress: value }));
    setAutoMapError('');
    setSiteMapManuallyCleared(false);
    setInventoryMapManuallyCleared(false);
    setShowAddressSuggestions(true);
  };

  const handleSelectAddressSuggestion = (feature) => {
    const fullAddress = feature.place_name || '';
    setBookInfo(b => ({ ...b, customerAddress: fullAddress, siteAddress: fullAddress }));
    setAddressSuggestions([]);
    setShowAddressSuggestions(false);
    loadMapsFromAddress(fullAddress, { feature });
  };

  const clearSiteMap = () => {
    setSiteMapFile(null);
    setSiteMapPath('');
    setSiteMapPreview(null);
    setSiteMapManuallyCleared(true);
  };

  const clearInventoryMap = () => {
    setInventoryMapFile(null);
    setInventoryMapPath('');
    setInventoryMapPreview(null);
    setInventoryMapManuallyCleared(true);
  };

  const handleClientChange = async (e) => {
    const id = e.target.value;
    const client = clients.find(c => c.id === id);
    const autoTemplate = client?.name?.toLowerCase().includes('bolchoz') ? 'bolchoz' : 'fourge';
    setBookInfo(b => ({
      ...b,
      clientId: id,
      clientName: client ? client.name : '',
      customerName: b.customerName || '',
      template: autoTemplate,
    }));
    if (id) {
      const { data: co } = await supabase.from('companies').select('*').eq('id', id).single();
      if (co) {
        setBookInfo(b => ({
          ...b,
          clientLogoUrl: co.client_logo_url || '',
          clientContactName: co.contact_name || '',
          clientContactEmail: co.contact_email || '',
          clientContactPhone: co.contact_phone || '',
          customerAddress: b.customerAddress || co.address || b.siteAddress || '',
          siteAddress: b.siteAddress || b.customerAddress || co.address || '',
        }));
      }
    }
  };

  const resetForm = () => {
    setBookInfo(EMPTY_BOOK_INFO);
    setSiteMapFile(null);
    setSiteMapPath('');
    setSiteMapPreview(null);
    setAutoMapAddress('');
    setAutoMapError('');
    setSiteMapManuallyCleared(false);
    setInventoryMapManuallyCleared(false);
    setAddressSuggestions([]);
    setShowAddressSuggestions(false);
    setInventoryMapFile(null);
    setInventoryMapPath('');
    setInventoryMapPreview(null);
    setSigns([EMPTY_SIGN()]);
    setPhotoItems([]);
    setCurrentId(null);
    setNotification(null);
    setProjectLogoFile(null);
    setProjectLogoPath('');
    setProjectLogoPreview('');
  };

  const handleNew = () => { resetForm(); setView('form'); };

  const buildLoadedState = async (book) => {
    const [siteMapSignedUrl, inventoryMapSignedUrl, projectLogoSignedUrl] = await Promise.all([
      getSignedUrl(book.site_map_path),
      getSignedUrl(book.inventory_map_path),
      getSignedUrl(book.project_logo_path),
    ]);
    const signsWithPreviews = await Promise.all((book.signs || []).map(async (sign) => {
      const [preview, existingPreview, recommendationPreview, signDetailPreview] = await Promise.all([
        getSignedUrl(sign.photoPath),
        getSignedUrl(sign.existingPhotoPath),
        getSignedUrl(sign.recommendationPhotoPath),
        getSignedUrl(sign.signDetailPhotoPath),
      ]);
      return {
        ...sign,
        photo: null,
        _photoPreview: preview || '',
        existingPhoto: null,
        _existingPhotoPreview: existingPreview || '',
        recommendationPhoto: null,
        _recommendationPhotoPreview: recommendationPreview || '',
        signDetailPhoto: null,
        _signDetailPhotoPreview: signDetailPreview || '',
      };
    }));
    const surveyItems = await Promise.all((book.survey_photo_paths || []).map(async (path) => {
      const preview = await getSignedUrl(path);
      return { file: null, path, preview: preview || '' };
    }));
    return { siteMapSignedUrl, inventoryMapSignedUrl, projectLogoSignedUrl, signsWithPreviews, surveyItems };
  };

  const handleLoad = async (book) => {
    setNotification(null);
    const { siteMapSignedUrl, inventoryMapSignedUrl, projectLogoSignedUrl, signsWithPreviews, surveyItems } = await buildLoadedState(book);
    setBookInfo({
      clientId: book.client_id || '',
      clientName: book.client_name || '',
      projectName: book.project_name || '',
      siteAddress: book.site_address || '',
      bookDate: book.book_date || new Date().toISOString().split('T')[0],
      preparedBy: book.prepared_by || '',
      revision: normalizeRevision(book.revision || '01'),
      template: book.template || 'fourge',
      creationDate: book.creation_date || new Date().toISOString().slice(0, 10),
      revisionDate: book.revision_date || '',
      customerName: book.customer_name || '',
      customerAddress: book.customer_address || book.site_address || '',
      clientLogoUrl: book.client_logo_url || '',
      clientContactName: book.client_contact_name || '',
      clientContactEmail: book.client_contact_email || '',
      clientContactPhone: book.client_contact_phone || '',
      approvedDate: book.approved_date || '',
      approvalNotes: book.approval_notes || '',
    });
    setSiteMapFile(null);
    setSiteMapPath(book.site_map_path || '');
    setSiteMapPreview(siteMapSignedUrl);
    setInventoryMapFile(null);
    setInventoryMapPath(book.inventory_map_path || '');
    setInventoryMapPreview(inventoryMapSignedUrl);
    setProjectLogoFile(null);
    setProjectLogoPath(book.project_logo_path || '');
    setProjectLogoPreview(projectLogoSignedUrl || '');
    setSigns(signsWithPreviews.length > 0 ? signsWithPreviews : [EMPTY_SIGN()]);
    setPhotoItems(surveyItems);
    setCurrentId(book.id);
    setView('form');
  };

  const handleDelete = async (book) => {
    if (!window.confirm('Delete this brand book? This cannot be undone.')) return;
    await cleanupBrandBookStorage(book);
    await supabase.from('brand_books').delete().eq('id', book.id);
    fetchBooks();
  };

  const handleSave = async () => {
    if (!bookInfo.clientName.trim()) {
      setNotification({ type: 'error', msg: 'Please select a client.' });
      return;
    }
    setSaving(true);
    setNotification(null);
    try {
      const bookId = currentId || crypto.randomUUID();

      // Upload project logo if new file
      let finalProjectLogoPath = projectLogoPath;
      if (projectLogoFile) {
        const ext = projectLogoFile.name.split('.').pop().toLowerCase();
        finalProjectLogoPath = `${bookId}/project-logo.${ext}`;
        await uploadFile(projectLogoFile, finalProjectLogoPath);
      }

      // Upload site map if new file
      let finalSiteMapPath = siteMapPath;
      if (siteMapFile) {
        const ext = siteMapFile.name.split('.').pop().toLowerCase();
        finalSiteMapPath = `${bookId}/site-map.${ext}`;
        await uploadFile(siteMapFile, finalSiteMapPath);
      }

      // Upload inventory map if new file
      let finalInventoryMapPath = inventoryMapPath;
      if (inventoryMapFile) {
        const ext = inventoryMapFile.name.split('.').pop().toLowerCase();
        finalInventoryMapPath = `${bookId}/inventory-map.${ext}`;
        await uploadFile(inventoryMapFile, finalInventoryMapPath);
      }

      // Upload sign photos if new file
      const finalSigns = await Promise.all(signs.map(async (sign) => {
        let photoPath = sign.photoPath || '';
        if (sign.photo) {
          const ext = sign.photo.name.split('.').pop().toLowerCase();
          photoPath = `${bookId}/sign-${sign._key}.${ext}`;
          await uploadFile(sign.photo, photoPath);
        }

        let existingPhotoPath = sign.existingPhotoPath || '';
        if (sign.existingPhoto) {
          const ext = sign.existingPhoto.name.split('.').pop().toLowerCase();
          existingPhotoPath = `${bookId}/sign-${sign._key}-existing.${ext}`;
          await uploadFile(sign.existingPhoto, existingPhotoPath);
        }

        let recommendationPhotoPath = sign.recommendationPhotoPath || '';
        if (sign.recommendationPhoto) {
          const ext = sign.recommendationPhoto.name.split('.').pop().toLowerCase();
          recommendationPhotoPath = `${bookId}/sign-${sign._key}-recommendation.${ext}`;
          await uploadFile(sign.recommendationPhoto, recommendationPhotoPath);
        }

        let signDetailPhotoPath = sign.signDetailPhotoPath || '';
        if (sign.signDetailPhoto) {
          const ext = sign.signDetailPhoto.name.split('.').pop().toLowerCase();
          signDetailPhotoPath = `${bookId}/sign-${sign._key}-detail.${ext}`;
          await uploadFile(sign.signDetailPhoto, signDetailPhotoPath);
        }

        const {
          photo: _photo,
          _photoPreview,
          existingPhoto: _existingPhoto,
          _existingPhotoPreview,
          recommendationPhoto: _recommendationPhoto,
          _recommendationPhotoPreview,
          signDetailPhoto: _signDetailPhoto,
          _signDetailPhotoPreview,
          ...rest
        } = sign;
        return { ...rest, photoPath, existingPhotoPath, recommendationPhotoPath, signDetailPhotoPath };
      }));

      // Upload survey photos if new file
      const finalSurveyPaths = await Promise.all(photoItems.map(async (item, i) => {
        if (item.file) {
          const ext = item.file.name.split('.').pop().toLowerCase();
          const path = `${bookId}/survey-${i}-${Date.now()}.${ext}`;
          await uploadFile(item.file, path);
          return path;
        }
        return item.path;
      }));

      const dbData = {
        id: bookId,
        client_id: bookInfo.clientId || null,
        client_name: bookInfo.clientName,
        project_name: bookInfo.projectName,
        site_address: bookInfo.siteAddress,
        book_date: bookInfo.bookDate || null,
        prepared_by: bookInfo.preparedBy,
        revision: normalizeRevision(bookInfo.revision),
        template: bookInfo.template || 'fourge',
        site_map_path: finalSiteMapPath,
        inventory_map_path: finalInventoryMapPath || null,
        signs: finalSigns,
        survey_photo_paths: finalSurveyPaths.filter(Boolean),
        updated_at: new Date().toISOString(),
        // Cover page fields
        project_logo_path: finalProjectLogoPath || null,
        creation_date: bookInfo.creationDate || null,
        revision_date: bookInfo.revisionDate || null,
        customer_name: bookInfo.customerName || null,
        customer_address: bookInfo.customerAddress || null,
        client_logo_url: bookInfo.clientLogoUrl || null,
        client_contact_name: bookInfo.clientContactName || null,
        client_contact_email: bookInfo.clientContactEmail || null,
        client_contact_phone: bookInfo.clientContactPhone || null,
        approved_date: bookInfo.approvedDate || null,
        approval_notes: bookInfo.approvalNotes || null,
      };

      const { error: upsertError } = await supabase.from('brand_books').upsert(dbData);
      if (upsertError) throw upsertError;
      const savedSignsWithPreviews = await Promise.all(finalSigns.map(async (sign) => {
        const [photoPreview, existingPhotoPreview, recommendationPhotoPreview, signDetailPhotoPreview] = await Promise.all([
          getSignedUrl(sign.photoPath),
          getSignedUrl(sign.existingPhotoPath),
          getSignedUrl(sign.recommendationPhotoPath),
          getSignedUrl(sign.signDetailPhotoPath),
        ]);

        return {
          ...sign,
          photo: null,
          existingPhoto: null,
          recommendationPhoto: null,
          signDetailPhoto: null,
          _photoPreview: photoPreview || '',
          _existingPhotoPreview: existingPhotoPreview || '',
          _recommendationPhotoPreview: recommendationPhotoPreview || '',
          _signDetailPhotoPreview: signDetailPhotoPreview || '',
        };
      }));
      setCurrentId(bookId);
      setSiteMapPath(finalSiteMapPath);
      setSiteMapFile(null);
      setInventoryMapPath(finalInventoryMapPath);
      setInventoryMapFile(null);
      setProjectLogoPath(finalProjectLogoPath);
      setProjectLogoFile(null);
      setSigns(savedSignsWithPreviews);
      setPhotoItems(finalSurveyPaths.filter(Boolean).map((path, i) => ({
        file: null,
        path,
        preview: photoItems[i]?.preview || '',
      })));
      await fetchBooks();
      setNotification({ type: 'success', msg: '✓ Brand book saved!' });
    } catch (err) {
      setNotification({ type: 'error', msg: `Save failed: ${err.message}` });
    } finally {
      setSaving(false);
    }
  };

  const handleGenerate = async () => {
    if (!bookInfo.clientName.trim()) {
      setNotification({ type: 'error', msg: 'Please select a client.' });
      return;
    }
    setGenerating(true);
    setNotification(null);
    try {
      const [siteMapSource, inventoryMapSource] = await Promise.all([
        siteMapFile ? Promise.resolve(siteMapFile) : (siteMapPath ? getSignedUrl(siteMapPath) : null),
        inventoryMapFile ? Promise.resolve(inventoryMapFile) : (inventoryMapPath ? getSignedUrl(inventoryMapPath) : null),
      ]);
      const signsWithSource = await Promise.all(signs.map(async s => ({
        ...s,
        photoSource: s.photo || (s.photoPath ? await getSignedUrl(s.photoPath) : null),
        existingPhotoSource: s.existingPhoto || (s.existingPhotoPath ? await getSignedUrl(s.existingPhotoPath) : null),
        recommendationPhotoSource: s.recommendationPhoto || (s.recommendationPhotoPath ? await getSignedUrl(s.recommendationPhotoPath) : null),
        signDetailPhotoSource: s.signDetailPhoto || (s.signDetailPhotoPath ? await getSignedUrl(s.signDetailPhotoPath) : null),
      })));
      const sitePhotoSources = await Promise.all(photoItems.map(async item =>
        item.file || (item.path ? await getSignedUrl(item.path) : null)
      ));
      const projectLogoSource = projectLogoFile || (projectLogoPath ? await getSignedUrl(projectLogoPath) : null);

      await generateBrandBookEditorPDF({
        template: bookInfo.template || 'fourge',
        clientName: bookInfo.clientName,
        projectName: bookInfo.projectName,
        siteAddress: bookInfo.siteAddress,
        bookDate: bookInfo.bookDate,
        preparedBy: bookInfo.preparedBy,
        revision: normalizeRevision(bookInfo.revision),
        siteMapSource,
        inventoryMapSource,
        signs: signsWithSource,
        sitePhotoSources,
        // Cover page
        projectLogoSource,
        creationDate: bookInfo.creationDate,
        revisionDate: bookInfo.revisionDate,
        customerName: bookInfo.customerName,
        customerAddress: bookInfo.customerAddress || bookInfo.siteAddress,
        clientLogoSource: bookInfo.clientLogoUrl || null,
        clientContactName: bookInfo.clientContactName,
        clientContactEmail: bookInfo.clientContactEmail,
        clientContactPhone: bookInfo.clientContactPhone,
        approvedDate: bookInfo.approvedDate,
        approvalNotes: bookInfo.approvalNotes,
      });
      setNotification({ type: 'success', msg: '✓ Brand book PDF downloaded!' });
    } catch (err) {
      setNotification({ type: 'error', msg: `Failed to generate PDF: ${err.message}` });
    } finally {
      setGenerating(false);
    }
  };

  // ── Project logo helpers ──────────────────────────────────────────────────────
  const handleProjectLogoUpload = (e) => {
    const file = e.target.files[0];
    if (!file) return;
    setProjectLogoFile(file);
    setProjectLogoPreview(URL.createObjectURL(file));
    setProjectLogoPath('');
  };

  // ── Client logo / info helpers ────────────────────────────────────────────────
  const handleClientLogoUpload = async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    if (!bookInfo.clientId) { setNotification({ type: 'error', msg: 'Select a client first.' }); return; }
    setUploadingClientLogo(true);
    const ext = file.name.split('.').pop().toLowerCase();
    const path = `${bookInfo.clientId}/logo.${ext}`;
    await supabase.storage.from('company-logos').remove([path]);
    const { error } = await supabase.storage.from('company-logos').upload(path, file, { upsert: true });
    if (!error) {
      const { data: { publicUrl } } = supabase.storage.from('company-logos').getPublicUrl(path);
      await supabase.from('companies').update({ client_logo_url: publicUrl }).eq('id', bookInfo.clientId);
      setBookInfo(b => ({ ...b, clientLogoUrl: publicUrl }));
    }
    setUploadingClientLogo(false);
  };

  const handleSaveClientInfo = async () => {
    if (!bookInfo.clientId) return;
    setSavingClientInfo(true);
    await supabase.from('companies').update({
      address: bookInfo.customerAddress || null,
      contact_name: bookInfo.clientContactName || null,
      contact_email: bookInfo.clientContactEmail || null,
      contact_phone: bookInfo.clientContactPhone || null,
    }).eq('id', bookInfo.clientId);
    setSavingClientInfo(false);
    setClientInfoSaved(true);
    setTimeout(() => setClientInfoSaved(false), 2500);
  };

  // ── Sign helpers ─────────────────────────────────────────────────────────────
  const updateSign = (key, field, value) =>
    setSigns(prev => prev.map(s => s._key === key ? { ...s, [field]: value } : s));
  const addSign = () => { setSigns(prev => [...prev, EMPTY_SIGN()]); };
  const removeSign = (key) => setSigns(prev => prev.filter(s => s._key !== key));
  const handleSignPhoto = (key, field, file) => {
    const previewField = `_${field}Preview`;
    const pathField = `${field}Path`;
    updateSign(key, field, file);
    updateSign(key, previewField, URL.createObjectURL(file));
    updateSign(key, pathField, '');
  };

  // ── Map helpers ───────────────────────────────────────────────────────────────
  const handleSiteMapFile = (file) => {
    setSiteMapFile(file);
    setSiteMapPath('');
    setSiteMapPreview(URL.createObjectURL(file));
    setAutoMapAddress('');
    setAutoMapError('');
    setSiteMapManuallyCleared(false);
  };

  const handleInventoryMapFile = (file) => {
    setInventoryMapFile(file);
    setInventoryMapPath('');
    setInventoryMapPreview(URL.createObjectURL(file));
    setAutoMapAddress('');
    setAutoMapError('');
    setInventoryMapManuallyCleared(false);
  };

  // ── Survey photo helpers ──────────────────────────────────────────────────────
  const handleSitePhotos = (files) => {
    const newItems = Array.from(files)
      .filter(isPhotoFile)
      .map(file => ({ file, path: '', preview: URL.createObjectURL(file) }));
    setPhotoItems(prev => [...prev, ...newItems]);
  };
  const removeSitePhoto = (i) => setPhotoItems(prev => prev.filter((_, idx) => idx !== i));

  // ── Unsaved changes indicator ─────────────────────────────────────────────────
  const hasUnsavedPhotos = siteMapFile || inventoryMapFile || signs.some(s => (
    s.photo || s.existingPhoto || s.recommendationPhoto || s.signDetailPhoto
  )) || photoItems.some(i => i.file);

  // ═══════════════════════════════════════════════════════════════════════════════
  // LIST VIEW
  // ═══════════════════════════════════════════════════════════════════════════════
  if (view === 'list') {
    const companyNames = [...new Set(savedBooks.map(book => book.client_name || clients.find(client => client.id === book.client_id)?.name).filter(Boolean))].sort();
    const filteredBooks = savedBooks.filter(book => {
      if (!filterCompany) return true;
      const clientName = book.client_name || clients.find(client => client.id === book.client_id)?.name;
      return clientName === filterCompany;
    });

    return (
      <Layout>
        <div className="page-header">
          <div>
            <div className="page-title">
              Brand Book Maker
            </div>
            <div className="page-subtitle">Saved brand books — click to edit or add a revision.</div>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-primary btn-sm" onClick={handleNew}>+ New Brand Book</button>
          </div>
        </div>

        {companyNames.length > 0 && (
        <div style={{ display: 'flex', gap: 12, marginBottom: 16 }}>
          <select className="filter-select" value={filterCompany} onChange={e => setFilterCompany(e.target.value)}>
            <option value="">All Companies</option>
            {companyNames.map(name => <option key={name} value={name}>{name}</option>)}
          </select>
        </div>
        )}

        {loadingBooks ? (
          <p style={{ padding: '24px 0', color: 'var(--text-muted)' }}>Loading...</p>
        ) : filteredBooks.length === 0 ? (
          <div className="card" style={{ minHeight: 160, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12 }}>
            <div style={{ fontSize: 13, color: 'var(--text-muted)', textAlign: 'center' }}>
              {savedBooks.length === 0 ? 'No brand books' : 'No matching brand books'}
            </div>
            {savedBooks.length === 0 && <button className="btn btn-primary" onClick={handleNew}>+ New Brand Book</button>}
          </div>
        ) : (
          <div className="table-wrapper">
            <table>
              <thead>
                <tr>
                  <SortTh col="project_name" sortKey={bbSortKey} sortDir={bbSortDir} onSort={bbToggle}>Name</SortTh>
                  <SortTh col="revision" sortKey={bbSortKey} sortDir={bbSortDir} onSort={bbToggle}>Revision</SortTh>
                  <SortTh col="sign_count" sortKey={bbSortKey} sortDir={bbSortDir} onSort={bbToggle}>Sign Count</SortTh>
                  <SortTh col="client" sortKey={bbSortKey} sortDir={bbSortDir} onSort={bbToggle}>Client</SortTh>
                  <SortTh col="updated_at" sortKey={bbSortKey} sortDir={bbSortDir} onSort={bbToggle}>Updated</SortTh>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {bbSort(filteredBooks, (book, key) => {
                  if (key === 'sign_count') return Array.isArray(book.signs) ? book.signs.length : 0;
                  if (key === 'client') return book.client_name || clients.find(c => c.id === book.client_id)?.name || '';
                  if (key === 'updated_at') return book.updated_at ? new Date(book.updated_at).getTime() : 0;
                  return book[key] || '';
                }).map(book => {
                  const signCount = Array.isArray(book.signs) ? book.signs.length : 0;
                  const clientName = book.client_name || clients.find(client => client.id === book.client_id)?.name || 'No client';
                  const updated = book.updated_at
                    ? new Date(book.updated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
                    : '—';

                  return (
                    <tr key={book.id} onClick={() => handleLoad(book)} style={{ cursor: 'pointer' }}>
                      <td style={{ fontWeight: 400 }}>{book.project_name || 'Brand Book'}</td>
                      <td>{`R${String(book.revision || '01').padStart(2, '0')}`}</td>
                      <td>{signCount}</td>
                      <td>{clientName}</td>
                      <td>{updated}</td>
                      <td onClick={e => e.stopPropagation()}>
                        <button
                          className="btn-icon btn-icon-danger"
                          onClick={() => handleDelete(book)}
                          title="Delete"
                        >
                          ✕
                        </button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </Layout>
    );
  }

  // ═══════════════════════════════════════════════════════════════════════════════
  // FORM VIEW
  // ═══════════════════════════════════════════════════════════════════════════════
  return (
    <Layout>
      <button className="back-link" onClick={() => setView('list')}>← All Brand Books</button>
      <div className="page-header">
        <div>
          <div className="page-title">
            {currentId
              ? `${bookInfo.projectName || 'Brand Book'} R${String(bookInfo.revision).padStart(2, '0')}`
              : 'New Brand Book'}
          </div>
          <div className="page-subtitle">
            {currentId ? 'Editing saved brand book' : 'Unsaved — fill in details and save'}
            {hasUnsavedPhotos && <span style={{ color: 'var(--accent)', marginLeft: 8 }}>· Unsaved photos</span>}
          </div>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <button className="btn btn-outline btn-sm" onClick={() => { if (window.confirm('Discard changes?')) { resetForm(); setView('list'); } }}>Discard</button>
          <button className="btn btn-outline btn-sm" onClick={handleSave} disabled={saving}>
            {saving ? 'Saving...' : 'Save'}
          </button>
          <LoadingButton className="btn btn-primary btn-sm" loading={generating} loadingText="Generating..." onClick={handleGenerate}>Generate PDF</LoadingButton>
          </div>
          {notification && (
            <span style={{ fontSize: 13, color: notification.type === 'error' ? 'var(--danger, #dc2626)' : 'var(--success, #16a34a)' }}>
              {notification.msg}
            </span>
          )}
        </div>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>

        {/* ── BRAND BOOK INFO ──────────────────────────────────────────────── */}
        <div className="card">
          <div className="card-title">Brand Book Info</div>
          <div className="grid-2">
            <div className="form-group">
              <label>Client *</label>
              <select value={bookInfo.clientId} onChange={handleClientChange}>
                <option value="">— Select client —</option>
                {clients.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
              </select>
            </div>
            <div className="form-group">
              <label>Project Name</label>
              <input type="text" placeholder="e.g. Main Street Rebrand 2025" value={bookInfo.projectName} onChange={set('projectName')} />
            </div>
          </div>
          <div className="form-group" style={{ maxWidth: 340 }}>
            <label>Template</label>
            <select value={bookInfo.template} onChange={set('template')}>
              {TEMPLATE_OPTIONS.map(t => <option key={t.value} value={t.value}>{t.label}</option>)}
            </select>
          </div>
          <div className="form-group" style={{ maxWidth: 180 }}>
            <label>Revision</label>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ fontWeight: 400, color: 'var(--text-primary)' }}>R</span>
              <input
                type="text"
                inputMode="numeric"
                pattern="[0-9]*"
                maxLength={2}
                value={bookInfo.revision}
                onChange={handleRevisionChange}
                onBlur={handleRevisionBlur}
                placeholder="00"
                style={{ maxWidth: 88 }}
              />
            </div>
          </div>
        </div>

        {/* ── COVER PAGE ───────────────────────────────────────────────────── */}
        <div className="card">
          <div className="card-title">Cover Page</div>

          {/* Project logo */}
          <div className="form-group">
            <label>Project Logo <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(top left, 5"×5" area)</span></label>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
              {projectLogoPreview && (
                <img src={projectLogoPreview} alt="Project logo" style={{ maxHeight: 60, maxWidth: 120, objectFit: 'contain', border: '1px solid var(--border)', borderRadius: 4, padding: 4, background: '#fff' }} />
              )}
              <div>
                <button className="btn btn-outline btn-sm" onClick={() => projectLogoRef.current?.click()}>
                  {projectLogoPreview ? 'Replace Logo' : 'Upload Logo'}
                </button>
                {projectLogoPreview && (
                  <button className="btn btn-outline btn-sm" style={{ marginLeft: 8, color: 'var(--danger)', borderColor: 'var(--danger)' }} onClick={() => { setProjectLogoFile(null); setProjectLogoPath(''); setProjectLogoPreview(''); }}>
                    Remove
                  </button>
                )}
                <input ref={projectLogoRef} type="file" accept={PHOTO_FILE_ACCEPT} style={{ display: 'none' }} onChange={handleProjectLogoUpload} />
              </div>
            </div>
          </div>

          {/* Dates */}
          <div className="grid-2">
            <div className="form-group">
              <label>Creation Date</label>
              <input type="date" value={bookInfo.creationDate} onChange={set('creationDate')} />
            </div>
            <div className="form-group">
              <label>Revision Date <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(optional)</span></label>
              <input type="date" value={bookInfo.revisionDate} onChange={set('revisionDate')} />
            </div>
          </div>

          {/* Customer info */}
          <div className="grid-2">
            <div className="form-group">
              <label>Customer Name</label>
              <input type="text" placeholder="e.g. Bolchoz Sign Solutions" value={bookInfo.customerName} onChange={set('customerName')} />
            </div>
            <div className="form-group">
              <label>Customer Address <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(optional)</span></label>
              <div style={{ position: 'relative' }}>
                <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                  <input
                    type="text"
                    placeholder="e.g. 123 Main St, City, State 00000"
                    value={bookInfo.customerAddress}
                    onChange={handleCustomerAddressChange}
                    onFocus={() => setShowAddressSuggestions(true)}
                    style={{ margin: 0 }}
                  />
                  <LoadingButton
                    className="btn btn-outline btn-sm"
                    loading={autoMapLoading}
                    disabled={autoMapLoading || !bookInfo.customerAddress.trim()}
                    loadingText="Loading..."
                    onClick={() => loadMapsFromAddress()}
                  >
                    Load Maps
                  </LoadingButton>
                </div>
                {showAddressSuggestions && (addressSuggestions.length > 0 || addressSuggesting) && (
                  <div
                    style={{
                      position: 'absolute',
                      top: 'calc(100% + 4px)',
                      left: 0,
                      right: 0,
                      zIndex: 20,
                      border: '1px solid var(--border)',
                      borderRadius: 4,
                      background: 'var(--card-bg)',
                      boxShadow: '0 16px 36px rgba(0,0,0,0.28)',
                      overflow: 'hidden',
                    }}
                  >
                    {addressSuggesting && (
                      <div style={{ padding: '10px 12px', fontSize: 12, color: 'var(--text-muted)' }}>Searching...</div>
                    )}
                    {addressSuggestions.map(feature => (
                      <button
                        key={feature.id}
                        type="button"
                        onClick={() => handleSelectAddressSuggestion(feature)}
                        style={{
                          display: 'block',
                          width: '100%',
                          textAlign: 'left',
                          padding: '10px 12px',
                          border: 'none',
                          borderTop: '1px solid var(--border)',
                          background: 'transparent',
                          color: 'var(--text-primary)',
                          cursor: 'pointer',
                          fontFamily: 'inherit',
                          fontSize: 13,
                        }}
                      >
                        {feature.place_name}
                      </button>
                    ))}
                  </div>
                )}
              </div>
              {autoMapError && (
                <div style={{ fontSize: 12, color: 'var(--danger)', marginTop: 6 }}>{autoMapError}</div>
              )}
            </div>
          </div>

          <div style={{ borderTop: '1px solid var(--border)', margin: '4px 0 20px' }} />

          {/* Client info (saved per company) */}
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
            <div>
              <div style={{ fontSize: 13, fontWeight: 400, color: 'var(--text-primary)' }}>Client Info</div>
              <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>Logo and contact saved to company — reused across all brand books.</div>
            </div>
            <button className="btn btn-outline btn-sm" onClick={handleSaveClientInfo} disabled={savingClientInfo || !bookInfo.clientId}>
              {savingClientInfo ? 'Saving...' : clientInfoSaved ? '✓ Saved' : 'Save to Company'}
            </button>
          </div>

          <div className="form-group">
            <label>Client Logo <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(3.5"×1.5" area, bottom right)</span></label>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
              {bookInfo.clientLogoUrl && (
                <img src={bookInfo.clientLogoUrl} alt="Client logo" style={{ maxHeight: 44, maxWidth: 130, objectFit: 'contain', border: '1px solid var(--border)', borderRadius: 4, padding: 4, background: '#fff' }} />
              )}
              <div>
                <button className="btn btn-outline btn-sm" onClick={() => clientLogoRef.current?.click()} disabled={uploadingClientLogo}>
                  {uploadingClientLogo ? 'Uploading...' : bookInfo.clientLogoUrl ? 'Replace Logo' : 'Upload Logo'}
                </button>
                {!bookInfo.clientId && <span style={{ marginLeft: 10, fontSize: 12, color: 'var(--text-muted)' }}>Select a client first</span>}
                <input ref={clientLogoRef} type="file" accept={PHOTO_FILE_ACCEPT} style={{ display: 'none' }} onChange={handleClientLogoUpload} />
              </div>
            </div>
          </div>

          <div className="grid-2">
            <div className="form-group">
              <label>Contact Name</label>
              <input type="text" placeholder="e.g. Jane Smith" value={bookInfo.clientContactName} onChange={set('clientContactName')} />
            </div>
            <div className="form-group">
              <label>Email</label>
              <input type="email" placeholder="e.g. jane@client.com" value={bookInfo.clientContactEmail} onChange={set('clientContactEmail')} />
            </div>
          </div>
          <div className="form-group" style={{ maxWidth: 320 }}>
            <label>Phone</label>
            <input type="text" placeholder="e.g. (555) 000-0000" value={bookInfo.clientContactPhone} onChange={set('clientContactPhone')} />
          </div>

          <div style={{ borderTop: '1px solid var(--border)', margin: '4px 0 20px' }} />

          {/* Approval */}
          <div style={{ fontSize: 13, fontWeight: 400, color: 'var(--text-primary)', marginBottom: 14 }}>Approval</div>
          <div className="form-group" style={{ maxWidth: 280 }}>
            <label>Approved Date <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(optional)</span></label>
            <input type="date" value={bookInfo.approvedDate} onChange={set('approvedDate')} />
          </div>
          <div className="form-group">
            <label>Approval Notes <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(optional)</span></label>
            <textarea placeholder="Any approval notes or conditions..." value={bookInfo.approvalNotes} onChange={set('approvalNotes')} style={{ minHeight: 70 }} />
          </div>
        </div>

        {/* ── SITE MAP ──────────────────────────────────────────────────────── */}
        <div className="card">
          <div className="card-title">Site Map</div>
          <div className="form-group">
            <label>Site Map Image <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(optional — gets its own dedicated page in the PDF)</span></label>
            <SiteMapDropZone
              preview={siteMapPreview}
              onFile={handleSiteMapFile}
              onClear={clearSiteMap}
              inputRef={siteMapRef}
            />
          </div>
        </div>

        {/* ── INVENTORY MAP ─────────────────────────────────────────────────── */}
        <div className="card">
          <div className="card-title">Site Map</div>
          <div className="form-group">
            <label>Inventory Map Image <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(shown on the left side of the sign inventory page — different from the site map)</span></label>
            <SiteMapDropZone
              preview={inventoryMapPreview}
              onFile={handleInventoryMapFile}
              onClear={clearInventoryMap}
              inputRef={inventoryMapRef}
            />
          </div>
        </div>

        {/* ── SIGNS ─────────────────────────────────────────────────────────── */}
        <div className="card">
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
            <div className="card-title" style={{ margin: 0 }}>Signs</div>
            <button className="btn btn-outline btn-sm" onClick={addSign}>+ Add Sign</button>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {signs.map((sign, i) => (
              <SignCard
                key={sign._key}
                sign={sign}
                index={i}
                onChange={(field, val) => updateSign(sign._key, field, val)}
                onPhotoChange={(field, file) => handleSignPhoto(sign._key, field, file)}
                onRemove={() => removeSign(sign._key)}
                canRemove={signs.length > 1}
                template={bookInfo.template}
              />
            ))}
          </div>
          <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
            <button className="btn btn-outline btn-sm" onClick={addSign}>+ Add Sign</button>
          </div>
        </div>

        {/* ── SITE PHOTOS ───────────────────────────────────────────────────── */}
        <div className="card">
          <div className="card-title">Site Photos</div>
          <div className="form-group">
            <label>Site Photos <span style={{ fontWeight: 400, color: 'var(--text-muted)' }}>(4×4 grid, 16 per page — additional pages added automatically)</span></label>
            <SitePhotosDropZone
              photoItems={photoItems}
              onFiles={handleSitePhotos}
              onRemove={removeSitePhoto}
              inputRef={sitePhotosRef}
            />
          </div>
        </div>

      </div>

      {/* Bottom actions */}
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8, marginTop: 24, paddingBottom: 40 }}>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-outline btn-sm" onClick={() => { if (window.confirm('Discard changes?')) { resetForm(); setView('list'); } }}>
            Discard
          </button>
          <button className="btn btn-outline btn-sm" onClick={handleSave} disabled={saving}>
            {saving ? 'Saving...' : 'Save'}
          </button>
          <LoadingButton className="btn btn-primary btn-sm" loading={generating} loadingText="Generating..." onClick={handleGenerate}>Generate PDF</LoadingButton>
        </div>
        {notification && (
          <span style={{ fontSize: 13, color: notification.type === 'error' ? 'var(--danger, #dc2626)' : 'var(--success, #16a34a)' }}>
            {notification.msg}
          </span>
        )}
      </div>
    </Layout>
  );
}

// ─── Book List Item ───────────────────────────────────────────────────────────
// ─── Sign Card ────────────────────────────────────────────────────────────────
function SignCard({ sign, index, onChange, onPhotoChange, onRemove, canRemove, template }) {
  const photoInputRef = useRef();
  const existingPhotoInputRef = useRef();
  const recommendationPhotoInputRef = useRef();
  const signDetailPhotoInputRef = useRef();
  const dragCounter = useRef(0);
  const [dragging, setDragging] = useState(false);

  const handleDragEnter = (e) => { e.preventDefault(); dragCounter.current++; setDragging(true); };
  const handleDragLeave = (e) => { e.preventDefault(); dragCounter.current--; if (dragCounter.current === 0) setDragging(false); };
  const handleDragOver = (e) => e.preventDefault();
  const handleDrop = (field) => (e) => {
    e.preventDefault(); dragCounter.current = 0; setDragging(false);
    const file = e.dataTransfer.files[0];
    if (isPhotoFile(file)) onPhotoChange(field, file);
  };

  const summary = [sign.type, sign.recommendation].filter(Boolean).join(' — ') || 'New Sign';
  const hasPhoto = sign._photoPreview || sign._existingPhotoPreview || sign._recommendationPhotoPreview || sign._signDetailPhotoPreview;

  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden' }}>
      <div style={{
        width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '10px 14px', background: 'var(--card-bg-2)', borderBottom: '1px solid var(--border)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{ fontSize: 10, fontWeight: 400, padding: '2px 8px', background: 'var(--accent)', color: '#1a1a1a', borderRadius: 4 }}>
            #{sign.signNumber || (index + 1)}
          </span>
          <span style={{ fontSize: 13, fontWeight: 400, color: 'var(--text-primary)' }}>{summary}</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {hasPhoto && <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>📷</span>}
          {sign.photo && <span style={{ fontSize: 11, color: 'var(--accent)' }}>unsaved photo</span>}
          {canRemove && (
            <span role="button" onClick={onRemove}
              style={{ fontSize: 13, color: 'var(--danger, #dc2626)', padding: '2px 6px', cursor: 'pointer' }}>✕</span>
          )}
        </div>
      </div>

      <div style={{ padding: 16 }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 }}>
          <div className="form-group" style={{ margin: 0 }}>
            <label>Sign # / ID</label>
            <input type="text" placeholder={`e.g. ${index + 1}`} value={sign.signNumber} onChange={e => onChange('signNumber', e.target.value)} />
          </div>
          <div className="form-group" style={{ margin: 0 }}>
            <label>Existing Type</label>
            <input type="text" placeholder="e.g. Monument, Channel Letter, Pylon…" value={sign.type} onChange={e => onChange('type', e.target.value)} />
          </div>
          <div className="form-group" style={{ margin: 0 }}>
            <label>Recommendation</label>
            <input type="text" placeholder="e.g. New Monument Sign" value={sign.recommendation} onChange={e => onChange('recommendation', e.target.value)} />
          </div>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 }}>
          <div className="form-group" style={{ margin: 0 }}>
            <label>Specifications</label>
            <textarea
              placeholder="Detailed specifications for the existing sign..."
              value={sign.specifications}
              onChange={e => onChange('specifications', e.target.value)}
              style={{ minHeight: 90 }}
            />
          </div>
          <div className="form-group" style={{ margin: 0 }}>
            <label>Notes</label>
            <textarea
              placeholder="Additional notes..."
              value={sign.notes}
              onChange={e => onChange('notes', e.target.value)}
              style={{ minHeight: 90 }}
            />
          </div>
        </div>

        {template === 'bolchoz' ? (
          <div style={{ display: 'grid', gridTemplateColumns: 'minmax(220px, 1fr) minmax(420px, 2fr)', gap: 12 }}>
            <PhotoField
              label="Existing Photo"
              preview={sign._existingPhotoPreview}
              fileName={sign.existingPhoto?.name}
              dragging={dragging}
              inputRef={existingPhotoInputRef}
              onDragEnter={handleDragEnter}
              onDragLeave={handleDragLeave}
              onDragOver={handleDragOver}
              onDrop={handleDrop('existingPhoto')}
              onPick={(file) => onPhotoChange('existingPhoto', file)}
            />
            <CombinedMockupPhotoField
              recommendationPreview={sign._recommendationPhotoPreview}
              recommendationFileName={sign.recommendationPhoto?.name}
              signDetailPreview={sign._signDetailPhotoPreview}
              signDetailFileName={sign.signDetailPhoto?.name}
              baseSourceImage={sign._existingPhotoPreview || sign._photoPreview || null}
              dragging={dragging}
              recommendationInputRef={recommendationPhotoInputRef}
              signDetailInputRef={signDetailPhotoInputRef}
              onDragEnter={handleDragEnter}
              onDragLeave={handleDragLeave}
              onDragOver={handleDragOver}
              onRecommendationDrop={handleDrop('recommendationPhoto')}
              onSignDetailDrop={handleDrop('signDetailPhoto')}
              onRecommendationPick={(file) => onPhotoChange('recommendationPhoto', file)}
              onSignDetailPick={(file) => onPhotoChange('signDetailPhoto', file)}
            />
          </div>
        ) : (
          <PhotoField
            label="Sign Photo"
            preview={sign._photoPreview}
            fileName={sign.photo?.name}
            dragging={dragging}
            inputRef={photoInputRef}
            onDragEnter={handleDragEnter}
            onDragLeave={handleDragLeave}
            onDragOver={handleDragOver}
            onDrop={handleDrop('photo')}
            onPick={(file) => onPhotoChange('photo', file)}
          />
        )}
      </div>
    </div>
  );
}

function PhotoField({ label, preview, fileName, dragging, inputRef, onDragEnter, onDragLeave, onDragOver, onDrop, onPick }) {
  return (
    <div className="form-group" style={{ margin: 0 }}>
      <label>{label}</label>
      <div
        onDragEnter={onDragEnter}
        onDragLeave={onDragLeave}
        onDragOver={onDragOver}
        onDrop={onDrop}
        onClick={() => inputRef.current?.click()}
        style={{
          border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
          borderRadius: 4,
          background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
          padding: 12,
          cursor: 'pointer',
          display: 'flex',
          alignItems: 'center',
          gap: 12,
          transition: 'border-color 0.15s, background 0.15s',
          minHeight: 60,
        }}
      >
        {preview ? (
          <>
            <img src={preview} alt={label} style={{ height: 60, width: 80, objectFit: 'cover', borderRadius: 4, flexShrink: 0 }} />
            <div>
              <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>
                {fileName || 'Saved photo'}
              </div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Click to replace · drag a new photo</div>
            </div>
          </>
        ) : (
          <div style={{ color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13 }}>
            {dragging ? '📂 Drop photo here' : '📷 Click to upload or drag & drop a photo'}
          </div>
        )}
      </div>
      <input
        ref={inputRef}
        type="file"
        accept={PHOTO_FILE_ACCEPT}
        style={{ display: 'none' }}
        onChange={e => { const f = e.target.files[0]; if (f) onPick(f); e.target.value = ''; }}
      />
    </div>
  );
}

// ─── Site Map Drop Zone ───────────────────────────────────────────────────────
function SiteMapDropZone({ preview, onFile, onClear, inputRef }) {
  const dragCounter = useRef(0);
  const [dragging, setDragging] = useState(false);
  const handleDragEnter = (e) => { e.preventDefault(); dragCounter.current++; setDragging(true); };
  const handleDragLeave = (e) => { e.preventDefault(); dragCounter.current--; if (dragCounter.current === 0) setDragging(false); };
  const handleDragOver = (e) => e.preventDefault();
  const handleDrop = (e) => {
    e.preventDefault(); dragCounter.current = 0; setDragging(false);
    const file = e.dataTransfer.files[0];
    if (isPhotoFile(file)) onFile(file);
  };

  if (preview) {
    return (
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 16 }}>
        <img src={preview} alt="site map" style={{ maxHeight: 160, maxWidth: 280, borderRadius: 4, border: '1px solid var(--border)', objectFit: 'contain' }} />
        <div>
          <button className="btn btn-outline btn-sm" onClick={onClear}>Remove</button>
          <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 8 }}>Click to replace</div>
        </div>
      </div>
    );
  }

  return (
    <>
      <div
        onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}
        onDragOver={handleDragOver} onDrop={handleDrop}
        onClick={() => inputRef.current?.click()}
        style={{
          border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
          borderRadius: 4,
          background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
          padding: '24px 16px', textAlign: 'center', cursor: 'pointer',
          color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13, transition: 'all 0.15s',
        }}
      >
        {dragging ? '📂 Drop site map here' : '🗺 Click to upload or drag & drop a site map image'}
      </div>
      <input ref={inputRef} type="file" accept={PHOTO_FILE_ACCEPT} style={{ display: 'none' }}
        onChange={e => { const f = e.target.files[0]; if (f) onFile(f); e.target.value = ''; }} />
    </>
  );
}

// ─── Survey Photos Drop Zone ──────────────────────────────────────────────────
function SitePhotosDropZone({ photoItems, onFiles, onRemove, inputRef }) {
  const dragCounter = useRef(0);
  const [dragging, setDragging] = useState(false);
  const handleDragEnter = (e) => { e.preventDefault(); dragCounter.current++; setDragging(true); };
  const handleDragLeave = (e) => { e.preventDefault(); dragCounter.current--; if (dragCounter.current === 0) setDragging(false); };
  const handleDragOver = (e) => e.preventDefault();
  const handleDrop = (e) => { e.preventDefault(); dragCounter.current = 0; setDragging(false); onFiles(e.dataTransfer.files); };

  return (
    <div>
      <div
        onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}
        onDragOver={handleDragOver} onDrop={handleDrop}
        onClick={() => inputRef.current?.click()}
        style={{
          border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
          borderRadius: 4,
          background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
          padding: '20px 16px', textAlign: 'center', cursor: 'pointer',
          color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13,
          marginBottom: photoItems.length > 0 ? 12 : 0, transition: 'all 0.15s',
        }}
      >
        {dragging ? '📂 Drop photos here' : '📷 Click to upload or drag & drop photos (select multiple)'}
      </div>
      <input ref={inputRef} type="file" accept={PHOTO_FILE_ACCEPT} multiple style={{ display: 'none' }}
        onChange={e => { if (e.target.files.length) { onFiles(e.target.files); e.target.value = ''; } }} />
      {photoItems.length > 0 && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
          {photoItems.map((item, i) => (
            <div key={i} style={{ position: 'relative' }}>
              <img
                src={item.preview}
                alt={item.file?.name || `photo ${i + 1}`}
                style={{ width: 80, height: 60, objectFit: 'cover', borderRadius: 4, border: '1px solid var(--border)', display: 'block' }}
              />
              {item.file && (
                <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, background: 'rgba(245,165,35,0.8)', fontSize: 8, textAlign: 'center', borderRadius: '0 0 4px 4px', padding: '1px 2px', color: '#1a1a1a', fontWeight: 400 }}>NEW</div>
              )}
              <button
                type="button"
                onClick={() => onRemove(i)}
                style={{
                  position: 'absolute', top: -6, right: -6,
                  background: '#dc2626', border: 'none', borderRadius: '50%',
                  width: 18, height: 18, fontSize: 10, color: '#fff',
                  cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', lineHeight: 1,
                }}
              >✕</button>
            </div>
          ))}
          <div style={{ fontSize: 11, color: 'var(--text-muted)', alignSelf: 'flex-end', marginLeft: 4 }}>
            {photoItems.length} photo{photoItems.length !== 1 ? 's' : ''}
            {photoItems.length > 12 && <span style={{ color: 'var(--accent)' }}> (first 12 shown in PDF)</span>}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Combined Mockup Photo Field (one editor for both outputs) ────────────────
function CombinedMockupPhotoField({
  recommendationPreview,
  recommendationFileName,
  signDetailPreview,
  signDetailFileName,
  baseSourceImage,
  dragging,
  recommendationInputRef,
  signDetailInputRef,
  onDragEnter,
  onDragLeave,
  onDragOver,
  onRecommendationDrop,
  onSignDetailDrop,
  onRecommendationPick,
  onSignDetailPick,
}) {
  const [showEditor, setShowEditor] = useState(false);

  const targetSource = (target) => {
    if (target === 'recommendation') return recommendationPreview || baseSourceImage;
    return signDetailPreview || recommendationPreview || baseSourceImage;
  };

  const tileStyle = {
    border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
    borderRadius: 4,
    background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
    padding: 12,
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    gap: 12,
    transition: 'border-color 0.15s, background 0.15s',
    minHeight: 60,
    position: 'relative',
  };

  const renderTile = ({ label, preview, fileName, inputRef, onDrop, onPick }) => (
    <div
      onDragEnter={onDragEnter}
      onDragLeave={onDragLeave}
      onDragOver={onDragOver}
      onDrop={onDrop}
      onClick={() => setShowEditor(true)}
      style={tileStyle}
    >
      {preview ? (
        <>
          <img src={preview} alt={label} style={{ height: 60, width: 80, objectFit: 'cover', borderRadius: 4, flexShrink: 0 }} />
          <div>
            <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>{fileName || 'Saved photo'}</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Click to open shared editor</div>
          </div>
        </>
      ) : (
        <div style={{ color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13 }}>
          {dragging ? 'Drop photo here' : `${label}: open editor or upload`}
        </div>
      )}
      <button
        type="button"
        onClick={(e) => { e.stopPropagation(); inputRef.current?.click(); }}
        style={{
          position: 'absolute', top: 6, right: 6,
          fontSize: 10, color: 'var(--text-muted)',
          background: 'var(--card-bg-2, var(--card-bg))',
          border: '1px solid var(--border)', borderRadius: 4,
          padding: '2px 7px', cursor: 'pointer', lineHeight: '16px',
        }}
      >
        Upload
      </button>
      <input
        ref={inputRef}
        type="file"
        accept={PHOTO_FILE_ACCEPT}
        style={{ display: 'none' }}
        onChange={e => { const f = e.target.files[0]; if (f) onPick(f); e.target.value = ''; }}
      />
    </div>
  );

  return (
    <div className="form-group" style={{ margin: 0 }}>
      <label>Mockup Photos</label>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
        {renderTile({
          label: 'Recommendation Photo',
          preview: recommendationPreview,
          fileName: recommendationFileName,
          inputRef: recommendationInputRef,
          onDrop: onRecommendationDrop,
          onPick: onRecommendationPick,
        })}
        {renderTile({
          label: 'Sign Detail Photo',
          preview: signDetailPreview,
          fileName: signDetailFileName,
          inputRef: signDetailInputRef,
          onDrop: onSignDetailDrop,
          onPick: onSignDetailPick,
        })}
      </div>
      <button
        type="button"
        className="btn btn-outline btn-sm"
        onClick={() => setShowEditor(true)}
        style={{ marginTop: 8 }}
      >
        Open Shared Mockup Editor
      </button>
      {showEditor && (
        <PhotoEditorModal
          title="Sign Mockup Editor"
          subtitle="Edit recommendation and sign detail photos from the same popup."
          targets={[
            {
              id: 'recommendation',
              label: 'Recommendation',
              sourceImage: targetSource('recommendation'),
              applyLabel: 'Apply to Recommendation',
              outputName: 'recommendation.jpg',
              downloadPrefix: 'recommendation-mockup',
            },
            {
              id: 'signDetail',
              label: 'Sign Detail',
              sourceImage: targetSource('signDetail'),
              applyLabel: 'Apply to Sign Detail',
              outputName: 'sign-detail-mockup.jpg',
              downloadPrefix: 'sign-detail-mockup',
            },
          ]}
          onApply={(file, targetId) => {
            if (targetId === 'signDetail') onSignDetailPick(file);
            else onRecommendationPick(file);
          }}
          onCancel={() => setShowEditor(false)}
        />
      )}
    </div>
  );
}

// ─── Recommendation Photo Field (click → editor, drag → direct upload) ────────
function RecommendationPhotoField({ preview, fileName, dragging, inputRef, onDragEnter, onDragLeave, onDragOver, onDrop, onPick, editorSourceImage }) {
  const [showEditor, setShowEditor] = useState(false);

  const handleUploadClick = (e) => {
    e.stopPropagation();
    inputRef.current?.click();
  };

  return (
    <div className="form-group" style={{ margin: 0 }}>
      <label>Recommendation Photo</label>
      <div
        onDragEnter={onDragEnter}
        onDragLeave={onDragLeave}
        onDragOver={onDragOver}
        onDrop={onDrop}
        onClick={() => setShowEditor(true)}
        style={{
          border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
          borderRadius: 4,
          background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
          padding: 12,
          cursor: 'pointer',
          display: 'flex',
          alignItems: 'center',
          gap: 12,
          transition: 'border-color 0.15s, background 0.15s',
          minHeight: 60,
          position: 'relative',
        }}
      >
        {preview ? (
          <>
            <img src={preview} alt="Recommendation Photo" style={{ height: 60, width: 80, objectFit: 'cover', borderRadius: 4, flexShrink: 0 }} />
            <div>
              <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>{fileName || 'Saved photo'}</div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Click to edit · drag a new photo</div>
            </div>
          </>
        ) : (
          <div style={{ color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13 }}>
            {dragging ? '📂 Drop photo here' : '✏️ Click to edit existing photo · or drag & drop'}
          </div>
        )}
        {/* Upload button sits in corner so it doesn't trigger the editor */}
        <button
          type="button"
          onClick={handleUploadClick}
          style={{
            position: 'absolute', top: 6, right: 6,
            fontSize: 10, color: 'var(--text-muted)',
            background: 'var(--card-bg-2, var(--card-bg))',
            border: '1px solid var(--border)', borderRadius: 4,
            padding: '2px 7px', cursor: 'pointer', lineHeight: '16px',
          }}
        >
          Upload file
        </button>
      </div>
      <input
        ref={inputRef}
        type="file"
        accept={PHOTO_FILE_ACCEPT}
        style={{ display: 'none' }}
        onChange={e => { const f = e.target.files[0]; if (f) onPick(f); e.target.value = ''; }}
      />
      {showEditor && (
        <PhotoEditorModal
          sourceImage={editorSourceImage}
          title="Sign Mockup Editor"
          subtitle="Import artwork, add dimensions, then save as the recommendation photo."
          applyLabel="Apply as Recommendation Photo"
          outputName="recommendation.jpg"
          downloadPrefix="recommendation-mockup"
          onApply={(file) => { onPick(file); setShowEditor(false); }}
          onCancel={() => setShowEditor(false)}
        />
      )}
    </div>
  );
}

// ─── Sign Detail Photo Field (click → dimension editor, drag → upload) ───────
function SignDetailPhotoField({ preview, fileName, dragging, inputRef, onDragEnter, onDragLeave, onDragOver, onDrop, onPick }) {
  const [showEditor, setShowEditor] = useState(false);

  const handleUploadClick = (e) => {
    e.stopPropagation();
    inputRef.current?.click();
  };

  return (
    <div className="form-group" style={{ margin: 0 }}>
      <label>Sign Detail Photo</label>
      <div
        onDragEnter={onDragEnter}
        onDragLeave={onDragLeave}
        onDragOver={onDragOver}
        onDrop={onDrop}
        onClick={() => preview && setShowEditor(true)}
        style={{
          border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
          borderRadius: 4,
          background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
          padding: 12,
          cursor: preview ? 'pointer' : 'default',
          display: 'flex',
          alignItems: 'center',
          gap: 12,
          transition: 'border-color 0.15s, background 0.15s',
          minHeight: 60,
          position: 'relative',
        }}
      >
        {preview ? (
          <>
            <img src={preview} alt="Sign Detail Photo" style={{ height: 60, width: 80, objectFit: 'cover', borderRadius: 4, flexShrink: 0 }} />
            <div>
              <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>{fileName || 'Saved photo'}</div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Click to add dimensions · drag a new photo</div>
            </div>
          </>
        ) : (
          <div style={{ color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13 }}>
            {dragging ? '📂 Drop photo here' : '📐 Upload a sign detail photo to add dimensions'}
          </div>
        )}
        <button
          type="button"
          onClick={handleUploadClick}
          style={{
            position: 'absolute', top: 6, right: 6,
            fontSize: 10, color: 'var(--text-muted)',
            background: 'var(--card-bg-2, var(--card-bg))',
            border: '1px solid var(--border)', borderRadius: 4,
            padding: '2px 7px', cursor: 'pointer', lineHeight: '16px',
          }}
        >
          Upload file
        </button>
      </div>
      <input
        ref={inputRef}
        type="file"
        accept={PHOTO_FILE_ACCEPT}
        style={{ display: 'none' }}
        onChange={e => { const f = e.target.files[0]; if (f) onPick(f); e.target.value = ''; }}
      />
      {showEditor && (
        <PhotoEditorModal
          sourceImage={preview}
          title="Sign Mockup Editor"
          subtitle="Import artwork, add dimensions, then save as the sign detail photo."
          applyLabel="Apply to Sign Detail Photo"
          outputName="sign-detail-mockup.jpg"
          downloadPrefix="sign-detail-mockup"
          onApply={(file) => { onPick(file); setShowEditor(false); }}
          onCancel={() => setShowEditor(false)}
        />
      )}
    </div>
  );
}

// ─── Dimension Editor ────────────────────────────────────────────────────────
function DimensionEditorModal({ sourceImage, onApply, onCancel }) {
  const canvasRef = useRef(null);
  const [baseImage, setBaseImage] = useState(null);
  const [loaded, setLoaded] = useState(false);
  const [dimensions, setDimensions] = useState([]);
  const [draftPoints, setDraftPoints] = useState([]);
  const [draftOffsetPoint, setDraftOffsetPoint] = useState(null);
  const [isPlacingOffset, setIsPlacingOffset] = useState(false);
  const [dimensionMode, setDimensionMode] = useState('line');
  const [boxStart, setBoxStart] = useState(null);
  const [boxEnd, setBoxEnd] = useState(null);
  const [activeDimensionId, setActiveDimensionId] = useState(null);
  const [dimensionText, setDimensionText] = useState('');
  const [boxWidthText, setBoxWidthText] = useState('');
  const [boxHeightText, setBoxHeightText] = useState('');
  const activeDimension = dimensions.find(item => item.id === activeDimensionId) || null;

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      const MAX_W = 960;
      const MAX_H = 580;
      const scale = Math.min(MAX_W / img.naturalWidth, MAX_H / img.naturalHeight, 1);
      canvas.width = Math.round(img.naturalWidth * scale);
      canvas.height = Math.round(img.naturalHeight * scale);
      setBaseImage(img);
      setLoaded(true);
    };
    img.onerror = () => {
      canvas.width = 800;
      canvas.height = 500;
      setLoaded(true);
    };
    img.src = sourceImage;
  }, [sourceImage]);

  useEffect(() => {
    renderDimensionCanvas(true);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [baseImage, dimensions, draftPoints, draftOffsetPoint, activeDimensionId, boxStart, boxEnd]);

  const getCanvasPos = (event) => {
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    const src = event.touches ? event.touches[0] : event;
    return {
      x: (src.clientX - rect.left) * scaleX,
      y: (src.clientY - rect.top) * scaleY,
    };
  };

  function drawDimension(ctx, item, selected = false) {
    const color = '#000000';
    const line = getDimensionLine(item);
    ctx.save();
    ctx.strokeStyle = color;
    ctx.fillStyle = color;
    const dimensionLineWidth = selected ? 3 : 2.25;
    ctx.lineWidth = dimensionLineWidth;

    // Extension lines from picked object points to the offset dimension line.
    [item.start, item.end].forEach((point, index) => {
      const target = index === 0 ? line.start : line.end;
      ctx.beginPath();
      ctx.moveTo(point.x, point.y);
      ctx.lineTo(target.x, target.y);
      ctx.stroke();
    });

    ctx.beginPath();
    ctx.moveTo(line.start.x, line.start.y);
    ctx.lineTo(line.end.x, line.end.y);
    ctx.stroke();

    const tickLength = 18;
    const tickAngle = (135 * Math.PI) / 180;
    ctx.lineWidth = dimensionLineWidth * 2;
    [line.start, line.end].forEach((point) => {
      ctx.beginPath();
      ctx.moveTo(point.x - Math.cos(tickAngle) * tickLength / 2, point.y - Math.sin(tickAngle) * tickLength / 2);
      ctx.lineTo(point.x + Math.cos(tickAngle) * tickLength / 2, point.y + Math.sin(tickAngle) * tickLength / 2);
      ctx.stroke();
    });
    ctx.lineWidth = dimensionLineWidth;

    const midX = (line.start.x + line.end.x) / 2;
    const midY = (line.start.y + line.end.y) / 2;
    ctx.font = '700 21px Helvetica, Arial, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    const label = item.label || 'Dimension';
    const width = ctx.measureText(label).width;
    const labelPaddingX = 10;
    const labelHeight = 30;
    const labelWidth = width + labelPaddingX * 2;
    const dx = line.end.x - line.start.x;
    const dy = line.end.y - line.start.y;
    const lineLength = Math.hypot(dx, dy);
    const isMostlyHorizontal = Math.abs(dx) >= Math.abs(dy);
    const needsOutsideLabel = isMostlyHorizontal
      ? lineLength < labelWidth + 28
      : lineLength < labelHeight + 28;
    let labelX = midX;
    let labelY = midY;

    if (needsOutsideLabel) {
      const leaderGap = 8;
      if (isMostlyHorizontal) {
        const leftPoint = line.start.x <= line.end.x ? line.start : line.end;
        const rightPoint = line.start.x <= line.end.x ? line.end : line.start;
        const leftRoom = leftPoint.x;
        const rightRoom = ctx.canvas.width - rightPoint.x;
        const placeLeft = leftRoom >= labelWidth + leaderGap || leftRoom >= rightRoom;
        labelX = placeLeft
          ? leftPoint.x - leaderGap - labelWidth / 2
          : rightPoint.x + leaderGap + labelWidth / 2;
        labelY = midY;
        const labelEdgeX = placeLeft ? labelX + labelWidth / 2 : labelX - labelWidth / 2;
        ctx.beginPath();
        ctx.moveTo(placeLeft ? leftPoint.x : rightPoint.x, midY);
        ctx.lineTo(labelEdgeX, labelY);
        ctx.stroke();
      } else {
        const topPoint = line.start.y <= line.end.y ? line.start : line.end;
        const bottomPoint = line.start.y <= line.end.y ? line.end : line.start;
        const topRoom = topPoint.y;
        const bottomRoom = ctx.canvas.height - bottomPoint.y;
        const placeTop = topRoom >= labelHeight + leaderGap || topRoom >= bottomRoom;
        labelX = midX;
        labelY = placeTop
          ? topPoint.y - leaderGap - labelHeight / 2
          : bottomPoint.y + leaderGap + labelHeight / 2;
        const labelEdgeY = placeTop ? labelY + labelHeight / 2 : labelY - labelHeight / 2;
        ctx.beginPath();
        ctx.moveTo(midX, placeTop ? topPoint.y : bottomPoint.y);
        ctx.lineTo(labelX, labelEdgeY);
        ctx.stroke();
      }
    }

    ctx.fillStyle = '#ffffff';
    ctx.fillRect(labelX - labelWidth / 2, labelY - labelHeight / 2, labelWidth, labelHeight);
    ctx.fillStyle = color;
    ctx.fillText(label, labelX, labelY);
    ctx.restore();
  }

  function getDimensionLine(item) {
    if (item.lineStart && item.lineEnd) {
      return { start: item.lineStart, end: item.lineEnd };
    }
    return { start: item.start, end: item.end };
  }

  function buildOffsetDimension(start, end, offsetPoint, label) {
    const dx = end.x - start.x;
    const dy = end.y - start.y;
    const len = Math.hypot(dx, dy) || 1;
    const nx = -dy / len;
    const ny = dx / len;
    const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
    const offset = (offsetPoint.x - mid.x) * nx + (offsetPoint.y - mid.y) * ny;
    return {
      id: crypto.randomUUID(),
      start,
      end,
      lineStart: { x: start.x + nx * offset, y: start.y + ny * offset },
      lineEnd: { x: end.x + nx * offset, y: end.y + ny * offset },
      label,
    };
  }

  function renderDimensionCanvas(showGuides) {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if (baseImage) {
      ctx.drawImage(baseImage, 0, 0, canvas.width, canvas.height);
    } else {
      ctx.fillStyle = '#e5e5e5';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
    dimensions.forEach(item => drawDimension(ctx, item, item.id === activeDimensionId));
    if (showGuides && draftPoints.length > 0) {
      ctx.fillStyle = '#000000';
      draftPoints.forEach((point) => {
        ctx.beginPath();
        ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
        ctx.fill();
      });
    }
    if (showGuides && draftPoints.length === 2 && draftOffsetPoint) {
      drawDimension(ctx, buildOffsetDimension(draftPoints[0], draftPoints[1], draftOffsetPoint, dimensionText || 'Dimension'), true);
    }
    if (showGuides && boxStart && boxEnd) {
      const rect = getRectFromPoints(boxStart, boxEnd);
      ctx.save();
      ctx.strokeStyle = '#000000';
      ctx.lineWidth = 2;
      ctx.setLineDash([6, 4]);
      ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
      ctx.setLineDash([]);
      ctx.restore();
    }
  }

  function getRectFromPoints(a, b) {
    const x = Math.min(a.x, b.x);
    const y = Math.min(a.y, b.y);
    return { x, y, w: Math.abs(b.x - a.x), h: Math.abs(b.y - a.y) };
  }

  function addBoxDimensions(rect) {
    const offset = 28;
    const groupId = crypto.randomUUID();
    const widthDim = {
      ...buildOffsetDimension(
        { x: rect.x, y: rect.y },
        { x: rect.x + rect.w, y: rect.y },
        { x: rect.x + rect.w / 2, y: rect.y - offset },
        boxWidthText || dimensionText || 'Width'
      ),
      group: 'box',
      groupId,
    };
    const heightDim = {
      ...buildOffsetDimension(
        { x: rect.x, y: rect.y },
        { x: rect.x, y: rect.y + rect.h },
        { x: rect.x - offset, y: rect.y + rect.h / 2 },
        boxHeightText || dimensionText || 'Height'
      ),
      group: 'box',
      groupId,
    };
    setDimensions(prev => [...prev, widthDim, heightDim]);
    setActiveDimensionId(widthDim.id);
  }

  function autoDetectBoxDimensions() {
    if (!baseImage) return;
    const canvas = canvasRef.current;
    if (!canvas) return;

    const detectCanvas = document.createElement('canvas');
    const maxDetectW = 260;
    const scale = Math.min(maxDetectW / canvas.width, 1);
    detectCanvas.width = Math.max(1, Math.round(canvas.width * scale));
    detectCanvas.height = Math.max(1, Math.round(canvas.height * scale));
    const detectCtx = detectCanvas.getContext('2d', { willReadFrequently: true });
    detectCtx.drawImage(baseImage, 0, 0, detectCanvas.width, detectCanvas.height);

    const { width, height } = detectCanvas;
    const image = detectCtx.getImageData(0, 0, width, height);
    const data = image.data;

    const columnCounts = new Uint16Array(width);
    const rowCounts = new Uint16Array(height);
    let nonWhiteCount = 0;
    const marginX = Math.round(width * 0.01);
    const marginY = Math.round(height * 0.01);

    for (let y = marginY; y < height - marginY; y++) {
      for (let x = marginX; x < width - marginX; x++) {
        const p = (y * width + x) * 4;
        const alpha = data[p + 3];
        const red = data[p];
        const green = data[p + 1];
        const blue = data[p + 2];
        const isWhite = red >= 245 && green >= 245 && blue >= 245;
        if (alpha > 24 && !isWhite) {
          columnCounts[x] += 1;
          rowCounts[y] += 1;
          nonWhiteCount += 1;
        }
      }
    }

    const minColumnHits = Math.max(2, Math.round(height * 0.006));
    const minRowHits = Math.max(2, Math.round(width * 0.006));
    let whiteBoundsMinX = -1;
    let whiteBoundsMaxX = -1;
    let whiteBoundsMinY = -1;
    let whiteBoundsMaxY = -1;

    for (let x = marginX; x < width - marginX; x++) {
      if (columnCounts[x] >= minColumnHits) {
        whiteBoundsMinX = x;
        break;
      }
    }
    for (let x = width - marginX - 1; x >= marginX; x--) {
      if (columnCounts[x] >= minColumnHits) {
        whiteBoundsMaxX = x;
        break;
      }
    }
    for (let y = marginY; y < height - marginY; y++) {
      if (rowCounts[y] >= minRowHits) {
        whiteBoundsMinY = y;
        break;
      }
    }
    for (let y = height - marginY - 1; y >= marginY; y--) {
      if (rowCounts[y] >= minRowHits) {
        whiteBoundsMaxY = y;
        break;
      }
    }

    const whiteBoundsW = whiteBoundsMaxX - whiteBoundsMinX;
    const whiteBoundsH = whiteBoundsMaxY - whiteBoundsMinY;
    const nonWhiteRatio = nonWhiteCount / ((width - marginX * 2) * (height - marginY * 2));
    const hasUsefulWhiteBounds =
      whiteBoundsMinX >= 0 &&
      whiteBoundsMinY >= 0 &&
      whiteBoundsW >= width * 0.03 &&
      whiteBoundsH >= height * 0.03 &&
      nonWhiteRatio >= 0.0005 &&
      nonWhiteRatio <= 0.65 &&
      (whiteBoundsW < width * 0.95 || whiteBoundsH < height * 0.95);

    if (hasUsefulWhiteBounds) {
      const padding = 4;
      const rectX = Math.max(0, whiteBoundsMinX / scale - padding);
      const rectY = Math.max(0, whiteBoundsMinY / scale - padding);
      addBoxDimensions({
        x: rectX,
        y: rectY,
        w: Math.min(canvas.width, whiteBoundsMaxX / scale + padding) - rectX,
        h: Math.min(canvas.height, whiteBoundsMaxY / scale + padding) - rectY,
      });
      return;
    }

    const luminance = new Float32Array(width * height);
    const gradients = [];

    for (let i = 0; i < width * height; i++) {
      const p = i * 4;
      luminance[i] = data[p] * 0.299 + data[p + 1] * 0.587 + data[p + 2] * 0.114;
    }
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        const i = y * width + x;
        gradients.push(Math.abs(luminance[i] - luminance[i + 1]) + Math.abs(luminance[i] - luminance[i + width]));
      }
    }

    if (!gradients.length) return;
    const mean = gradients.reduce((sum, value) => sum + value, 0) / gradients.length;
    const variance = gradients.reduce((sum, value) => sum + Math.pow(value - mean, 2), 0) / gradients.length;
    const threshold = Math.max(24, Math.min(70, mean + Math.sqrt(variance) * 1.1));
    const edges = new Uint8Array(width * height);
    const borderX = Math.round(width * 0.03);
    const borderY = Math.round(height * 0.03);

    for (let y = borderY; y < height - borderY; y++) {
      for (let x = borderX; x < width - borderX; x++) {
        const i = y * width + x;
        const gradient = Math.abs(luminance[i] - luminance[i + 1]) + Math.abs(luminance[i] - luminance[i + width]);
        if (gradient >= threshold) edges[i] = 1;
      }
    }

    const visited = new Uint8Array(width * height);
    const centerX = width / 2;
    const centerY = height / 2;
    let best = null;

    for (let y = borderY; y < height - borderY; y++) {
      for (let x = borderX; x < width - borderX; x++) {
        const startIndex = y * width + x;
        if (!edges[startIndex] || visited[startIndex]) continue;

        const stack = [startIndex];
        visited[startIndex] = 1;
        let count = 0;
        let minX = x;
        let maxX = x;
        let minY = y;
        let maxY = y;

        while (stack.length) {
          const index = stack.pop();
          const px = index % width;
          const py = Math.floor(index / width);
          count += 1;
          minX = Math.min(minX, px);
          maxX = Math.max(maxX, px);
          minY = Math.min(minY, py);
          maxY = Math.max(maxY, py);

          for (let oy = -1; oy <= 1; oy++) {
            for (let ox = -1; ox <= 1; ox++) {
              if (!ox && !oy) continue;
              const nx = px + ox;
              const ny = py + oy;
              if (nx < borderX || nx >= width - borderX || ny < borderY || ny >= height - borderY) continue;
              const nextIndex = ny * width + nx;
              if (!edges[nextIndex] || visited[nextIndex]) continue;
              visited[nextIndex] = 1;
              stack.push(nextIndex);
            }
          }
        }

        const boxW = maxX - minX;
        const boxH = maxY - minY;
        if (count < 18 || boxW < width * 0.04 || boxH < height * 0.04) continue;
        const boxCenterX = (minX + maxX) / 2;
        const boxCenterY = (minY + maxY) / 2;
        const centerPenalty = Math.hypot((boxCenterX - centerX) / width, (boxCenterY - centerY) / height) * 120;
        const score = count + boxW * boxH * 0.02 - centerPenalty;
        if (!best || score > best.score) best = { minX, maxX, minY, maxY, score };
      }
    }

    if (!best) {
      window.alert('Could not detect a clear sign/object area. Drag a box manually on the image.');
      return;
    }

    const padding = 10;
    const rect = {
      x: Math.max(0, best.minX / scale - padding),
      y: Math.max(0, best.minY / scale - padding),
      w: Math.min(canvas.width, best.maxX / scale + padding) - Math.max(0, best.minX / scale - padding),
      h: Math.min(canvas.height, best.maxY / scale + padding) - Math.max(0, best.minY / scale - padding),
    };
    addBoxDimensions(rect);
  }

  const findDimensionAtPoint = (point) => {
    const distanceToSegment = (pointValue, start, end) => {
      const dx = end.x - start.x;
      const dy = end.y - start.y;
      const lenSq = dx * dx + dy * dy || 1;
      const t = Math.max(0, Math.min(1, ((pointValue.x - start.x) * dx + (pointValue.y - start.y) * dy) / lenSq));
      const x = start.x + t * dx;
      const y = start.y + t * dy;
      return Math.hypot(pointValue.x - x, pointValue.y - y);
    };
      return dimensions.find(item => {
        const line = getDimensionLine(item);
        return distanceToSegment(point, line.start, line.end) < 10;
      }) || null;
  };

  const handlePointerDown = (e) => {
    e.preventDefault();
    const point = getCanvasPos(e);
    if (dimensionMode === 'box') {
      setBoxStart(point);
      setBoxEnd(point);
      setActiveDimensionId(null);
      return;
    }
    if (draftPoints.length === 2) {
      setIsPlacingOffset(true);
      setDraftOffsetPoint(point);
      return;
    }
    const existing = findDimensionAtPoint(point);
    if (existing) {
      setActiveDimensionId(existing.id);
      setDimensionText(existing.label || '');
      return;
    }
    setDraftPoints(prev => prev.length >= 2 ? [point] : [...prev, point]);
    setDraftOffsetPoint(null);
    setActiveDimensionId(null);
  };

  const handlePointerMove = (e) => {
    if (dimensionMode === 'box' && boxStart) {
      e.preventDefault();
      setBoxEnd(getCanvasPos(e));
      return;
    }
    if (!isPlacingOffset || draftPoints.length !== 2) return;
    e.preventDefault();
    setDraftOffsetPoint(getCanvasPos(e));
  };

  const handlePointerUp = () => {
    if (dimensionMode === 'box') {
      if (!boxStart || !boxEnd) return;
      const rect = getRectFromPoints(boxStart, boxEnd);
      if (rect.w < 8 || rect.h < 8) {
        setBoxStart(null);
        setBoxEnd(null);
        return;
      }
      addBoxDimensions(rect);
      setBoxStart(null);
      setBoxEnd(null);
      return;
    }
    if (!isPlacingOffset || draftPoints.length !== 2 || !draftOffsetPoint) return;
    if (Math.hypot(draftPoints[1].x - draftPoints[0].x, draftPoints[1].y - draftPoints[0].y) < 8) {
      setDraftPoints([]);
      setDraftOffsetPoint(null);
      setIsPlacingOffset(false);
      return;
    }
    const label = dimensionText || 'Dimension';
    const next = buildOffsetDimension(draftPoints[0], draftPoints[1], draftOffsetPoint, label);
    setDimensions(prev => [...prev, next]);
    setActiveDimensionId(next.id);
    setDraftPoints([]);
    setDraftOffsetPoint(null);
    setIsPlacingOffset(false);
  };

  const updateActiveLabel = () => {
    if (!activeDimensionId) return;
    setDimensions(prev => prev.map(item => item.id === activeDimensionId ? { ...item, label: dimensionText || 'Dimension' } : item));
  };

  const removeActiveDimension = () => {
    if (!activeDimensionId) return;
    const selected = dimensions.find(item => item.id === activeDimensionId);
    setDimensions(prev => {
      if (selected?.group === 'box' && selected.groupId) {
        return prev.filter(item => item.groupId !== selected.groupId);
      }
      return prev.filter(item => item.id !== activeDimensionId);
    });
    setActiveDimensionId(null);
    setDimensionText('');
  };

  const switchDimensionMode = (mode) => {
    setDimensionMode(mode);
    setDraftPoints([]);
    setDraftOffsetPoint(null);
    setIsPlacingOffset(false);
    setBoxStart(null);
    setBoxEnd(null);
  };

  const handleApply = () => {
    renderDimensionCanvas(false);
    canvasRef.current.toBlob(blob => {
      onApply(new File([blob], 'sign-detail-dimensions.jpg', { type: 'image/jpeg' }));
    }, 'image/jpeg', 0.92);
    requestAnimationFrame(() => renderDimensionCanvas(true));
  };

  return (
    <div
      style={{ ...popupOverlayStyle, zIndex: 9999, padding: 20 }}
      onClick={(e) => { if (e.target === e.currentTarget) onCancel(); }}
    >
      <div style={{ ...popupSurfaceStyle, borderRadius: 8, display: 'flex', flexDirection: 'column', maxWidth: '98vw', maxHeight: '96vh', overflow: 'hidden', padding: 0 }}>
        <div style={{ padding: '12px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 400, color: 'var(--text-primary)' }}>Sign Detail Dimensions</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>Add line dimensions or drag a box to auto-place width and height callouts.</div>
          </div>
          <button onClick={onCancel} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: 18, lineHeight: 1, padding: '0 2px' }}>✕</button>
        </div>
        <div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', background: 'var(--card-bg-2, var(--card-bg))', flexShrink: 0 }}>
          <div style={{ display: 'flex', border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden' }}>
            {[
              ['line', 'Line'],
              ['box', 'Box'],
            ].map(([mode, label]) => (
              <button
                key={mode}
                className={`btn btn-sm ${dimensionMode === mode ? 'btn-primary' : 'btn-outline'}`}
                style={{ border: 'none', borderRadius: 0 }}
                onClick={() => switchDimensionMode(mode)}
              >
                {label}
              </button>
            ))}
          </div>
          <input
            type="text"
            value={dimensionText}
            onChange={e => setDimensionText(e.target.value)}
            placeholder='e.g. 48" W, 24" H, 8 ft'
            style={{ minHeight: 32, margin: 0, width: 220 }}
          />
          {dimensionMode === 'box' && (
            <>
              <input
                type="text"
                value={boxWidthText}
                onChange={e => setBoxWidthText(e.target.value)}
                placeholder='Width e.g. 48"'
                style={{ minHeight: 32, margin: 0, width: 130 }}
              />
              <input
                type="text"
                value={boxHeightText}
                onChange={e => setBoxHeightText(e.target.value)}
                placeholder='Height e.g. 24"'
                style={{ minHeight: 32, margin: 0, width: 130 }}
              />
              <button className="btn btn-outline btn-sm" onClick={autoDetectBoxDimensions} disabled={!loaded || !baseImage}>Auto Box</button>
            </>
          )}
          <button className="btn btn-outline btn-sm" onClick={updateActiveLabel} disabled={!activeDimension}>Update Selected</button>
          <button className="btn btn-outline btn-sm" onClick={removeActiveDimension} disabled={!activeDimension}>Remove Selected</button>
          <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
            {dimensionMode === 'box'
              ? 'Drag a box around the artwork/sign area to auto-add width and height.'
              : (
                <>
                  {draftPoints.length === 0 && 'Click first point.'}
                  {draftPoints.length === 1 && 'Click second point.'}
                  {draftPoints.length === 2 && 'Drag outward to place the dimension line.'}
                  {draftPoints.length === 0 && ' Click an existing dimension to edit it.'}
                </>
              )}
          </span>
        </div>
        <div style={{ overflow: 'auto', flex: 1, background: '#2a2a2a', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', position: 'relative' }}>
          {!loaded && <div style={{ padding: 48, color: '#888', fontSize: 13 }}>Loading image…</div>}
          <canvas
            ref={canvasRef}
            style={{ display: loaded ? 'block' : 'none', cursor: 'crosshair', maxWidth: '100%', touchAction: 'none', userSelect: 'none' }}
            onPointerDown={handlePointerDown}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerUp}
            onPointerLeave={handlePointerUp}
          />
        </div>
        <div style={{ padding: '12px 18px', borderTop: '1px solid var(--border)', display: 'flex', justifyContent: 'flex-end', gap: 8, flexShrink: 0 }}>
          <button className="btn btn-outline btn-sm" onClick={onCancel}>Cancel</button>
          <button className="btn btn-primary btn-sm" onClick={handleApply}>Apply to Sign Detail Photo</button>
        </div>
      </div>
    </div>
  );
}

// ─── Artwork Mockup Editor ───────────────────────────────────────────────────
const HANDLE_SIZE = 12;

function makeArtworkPoints(x, y, w, h) {
  return [
    { x, y },
    { x: x + w, y },
    { x: x + w, y: y + h },
    { x, y: y + h },
  ];
}

function cloneArtworkState(items) {
  return items.map(item => ({
    ...item,
    points: item.points.map(point => ({ ...point })),
  }));
}

function getArtworkBounds(points) {
  const xs = points.map(p => p.x);
  const ys = points.map(p => p.y);
  const minX = Math.min(...xs);
  const maxX = Math.max(...xs);
  const minY = Math.min(...ys);
  const maxY = Math.max(...ys);
  return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}

function getArtworkHandles(artwork) {
  const labels = ['nw', 'ne', 'se', 'sw'];
  return artwork.points.map((point, index) => ({ ...point, id: labels[index], index }));
}

function getRotateHandle(artwork) {
  const [topLeft, topRight] = artwork.points;
  const midX = (topLeft.x + topRight.x) / 2;
  const midY = (topLeft.y + topRight.y) / 2;
  const dx = topRight.x - topLeft.x;
  const dy = topRight.y - topLeft.y;
  const length = Math.hypot(dx, dy) || 1;
  return {
    x: midX - (dy / length) * 34,
    y: midY + (dx / length) * 34,
  };
}

function pointInPolygon(point, polygon) {
  let inside = false;
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i, i += 1) {
    const xi = polygon[i].x;
    const yi = polygon[i].y;
    const xj = polygon[j].x;
    const yj = polygon[j].y;
    const intersects = ((yi > point.y) !== (yj > point.y)) &&
      (point.x < ((xj - xi) * (point.y - yi)) / (yj - yi || 1) + xi);
    if (intersects) inside = !inside;
  }
  return inside;
}

function interpolateQuad(points, u, v) {
  const [p0, p1, p2, p3] = points;
  return {
    x: (1 - u) * (1 - v) * p0.x + u * (1 - v) * p1.x + u * v * p2.x + (1 - u) * v * p3.x,
    y: (1 - u) * (1 - v) * p0.y + u * (1 - v) * p1.y + u * v * p2.y + (1 - u) * v * p3.y,
  };
}

function rotatePoints(points, degrees) {
  const radians = (degrees * Math.PI) / 180;
  const cos = Math.cos(radians);
  const sin = Math.sin(radians);
  const center = points.reduce((acc, point) => ({ x: acc.x + point.x / points.length, y: acc.y + point.y / points.length }), { x: 0, y: 0 });
  return points.map(point => {
    const dx = point.x - center.x;
    const dy = point.y - center.y;
    return {
      x: center.x + dx * cos - dy * sin,
      y: center.y + dx * sin + dy * cos,
    };
  });
}

function drawTriangleImage(ctx, img, s0, s1, s2, d0, d1, d2) {
  const denom = s0.x * (s1.y - s2.y) + s1.x * (s2.y - s0.y) + s2.x * (s0.y - s1.y);
  if (!denom) return;
  const a = (d0.x * (s1.y - s2.y) + d1.x * (s2.y - s0.y) + d2.x * (s0.y - s1.y)) / denom;
  const c = (d0.x * (s2.x - s1.x) + d1.x * (s0.x - s2.x) + d2.x * (s1.x - s0.x)) / denom;
  const e = (d0.x * (s1.x * s2.y - s2.x * s1.y) + d1.x * (s2.x * s0.y - s0.x * s2.y) + d2.x * (s0.x * s1.y - s1.x * s0.y)) / denom;
  const b = (d0.y * (s1.y - s2.y) + d1.y * (s2.y - s0.y) + d2.y * (s0.y - s1.y)) / denom;
  const d = (d0.y * (s2.x - s1.x) + d1.y * (s0.x - s2.x) + d2.y * (s1.x - s0.x)) / denom;
  const f = (d0.y * (s1.x * s2.y - s2.x * s1.y) + d1.y * (s2.x * s0.y - s0.x * s2.y) + d2.y * (s0.x * s1.y - s1.x * s0.y)) / denom;

  ctx.save();
  ctx.beginPath();
  ctx.moveTo(d0.x, d0.y);
  ctx.lineTo(d1.x, d1.y);
  ctx.lineTo(d2.x, d2.y);
  ctx.closePath();
  ctx.clip();
  ctx.transform(a, b, c, d, e, f);
  ctx.drawImage(img, 0, 0);
  ctx.restore();
}

function drawImageInQuad(ctx, img, points) {
  const steps = 10;
  for (let y = 0; y < steps; y += 1) {
    for (let x = 0; x < steps; x += 1) {
      const u0 = x / steps;
      const u1 = (x + 1) / steps;
      const v0 = y / steps;
      const v1 = (y + 1) / steps;
      const d00 = interpolateQuad(points, u0, v0);
      const d10 = interpolateQuad(points, u1, v0);
      const d11 = interpolateQuad(points, u1, v1);
      const d01 = interpolateQuad(points, u0, v1);
      const s00 = { x: u0 * img.naturalWidth, y: v0 * img.naturalHeight };
      const s10 = { x: u1 * img.naturalWidth, y: v0 * img.naturalHeight };
      const s11 = { x: u1 * img.naturalWidth, y: v1 * img.naturalHeight };
      const s01 = { x: u0 * img.naturalWidth, y: v1 * img.naturalHeight };
      drawTriangleImage(ctx, img, s00, s10, s11, d00, d10, d11);
      drawTriangleImage(ctx, img, s00, s11, s01, d00, d11, d01);
    }
  }
}

function PhotoEditorModal({
  sourceImage,
  onApply,
  onCancel,
  title = 'Sign Mockup Editor',
  subtitle = 'Import artwork, add dimensions, then save the edited photo.',
  applyLabel = 'Apply Edited Photo',
  outputName = 'sign-mockup.jpg',
  downloadPrefix = 'sign-mockup',
  targets = null,
}) {
  const canvasRef = useRef(null);
  const artworkInputRef = useRef(null);
  const dragRef = useRef(null);
  const layerDragRef = useRef(null);
  const [baseImage, setBaseImage] = useState(null);
  const [artworks, setArtworks] = useState([]);
  const [dimensions, setDimensions] = useState([]);
  const [history, setHistory] = useState([[]]);
  const [historyIndex, setHistoryIndex] = useState(0);
  const [activeTool, setActiveTool] = useState('select');
  const [selectedArtworkId, setSelectedArtworkId] = useState(null);
  const [activeDimensionId, setActiveDimensionId] = useState(null);
  const [loaded, setLoaded] = useState(false);
  const [calibrating, setCalibrating] = useState(false);
  const [calibrationPoints, setCalibrationPoints] = useState([]);
  const [calibrationDistance, setCalibrationDistance] = useState('');
  const [sizeUnit, setSizeUnit] = useState('in');
  const [artworkWidth, setArtworkWidth] = useState('');
  const [artworkHeight, setArtworkHeight] = useState('');
  const [lockRatio, setLockRatio] = useState(true);
  const [draftPoints, setDraftPoints] = useState([]);
  const [draftOffsetPoint, setDraftOffsetPoint] = useState(null);
  const [isPlacingOffset, setIsPlacingOffset] = useState(false);
  const [boxStart, setBoxStart] = useState(null);
  const [boxEnd, setBoxEnd] = useState(null);
  const [dimensionText, setDimensionText] = useState('');
  const [boxWidthText, setBoxWidthText] = useState('');
  const [boxHeightText, setBoxHeightText] = useState('');
  const [activeTargetId, setActiveTargetId] = useState(targets?.[0]?.id || 'single');
  const [targetSources, setTargetSources] = useState(() => Object.fromEntries((targets || []).map(target => [target.id, target.sourceImage || ''])));
  const activeTarget = targets?.find(target => target.id === activeTargetId) || null;
  const effectiveSourceImage = activeTarget ? targetSources[activeTarget.id] : sourceImage;
  const effectiveApplyLabel = activeTarget?.applyLabel || applyLabel;
  const effectiveOutputName = activeTarget?.outputName || outputName;
  const effectiveDownloadPrefix = activeTarget?.downloadPrefix || downloadPrefix;
  const selectedArtwork = artworks.find(item => item.id === selectedArtworkId) || null;
  const activeDimension = dimensions.find(item => item.id === activeDimensionId) || null;

  const commitArtworks = (updater) => {
    setArtworks(prev => {
      const next = typeof updater === 'function' ? updater(prev) : updater;
      setHistory(current => {
        const trimmed = current.slice(0, historyIndex + 1);
        return [...trimmed, cloneArtworkState(next)].slice(-40);
      });
      setHistoryIndex(index => Math.min(index + 1, 39));
      return next;
    });
  };

  const setArtworksLive = (updater) => {
    setArtworks(prev => typeof updater === 'function' ? updater(prev) : updater);
  };

  const resetEditorLayers = () => {
    setArtworks([]);
    setDimensions([]);
    setHistory([[]]);
    setHistoryIndex(0);
    setSelectedArtworkId(null);
    setActiveDimensionId(null);
    setCalibrationPoints([]);
    setCalibrating(false);
    setDraftPoints([]);
    setDraftOffsetPoint(null);
    setIsPlacingOffset(false);
    setBoxStart(null);
    setBoxEnd(null);
  };

  useEffect(() => {
    if (!targets) return;
    setTargetSources(Object.fromEntries(targets.map(target => [target.id, target.sourceImage || ''])));
  }, [targets]);

  useEffect(() => {
    resetEditorLayers();
  }, [activeTargetId]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');

    const initBlank = () => {
      canvas.width = 800;
      canvas.height = 500;
      ctx.fillStyle = '#e5e5e5';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      setBaseImage(null);
      setLoaded(true);
    };

    setLoaded(false);
    if (!effectiveSourceImage) { initBlank(); return; }

    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      const MAX_W = 960;
      const MAX_H = 580;
      const scale = Math.min(MAX_W / img.naturalWidth, MAX_H / img.naturalHeight, 1);
      canvas.width  = Math.round(img.naturalWidth  * scale);
      canvas.height = Math.round(img.naturalHeight * scale);
      setBaseImage(img);
      setLoaded(true);
    };
    img.onerror = initBlank;
    img.src = effectiveSourceImage;
  }, [effectiveSourceImage]);

  useEffect(() => {
    renderCanvas(true);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [baseImage, artworks, dimensions, selectedArtworkId, activeDimensionId, calibrationPoints, draftPoints, draftOffsetPoint, boxStart, boxEnd]);

  const getCanvasPos = (event) => {
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    const src = event.touches ? event.touches[0] : event;
    return {
      x: (src.clientX - rect.left) * scaleX,
      y: (src.clientY - rect.top)  * scaleY,
    };
  };

  function getDimensionLine(item) {
    if (item.lineStart && item.lineEnd) return { start: item.lineStart, end: item.lineEnd };
    return { start: item.start, end: item.end };
  }

  function buildOffsetDimension(start, end, offsetPoint, label) {
    const dx = end.x - start.x;
    const dy = end.y - start.y;
    const len = Math.hypot(dx, dy) || 1;
    const nx = -dy / len;
    const ny = dx / len;
    const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
    const offset = (offsetPoint.x - mid.x) * nx + (offsetPoint.y - mid.y) * ny;
    return {
      id: crypto.randomUUID(),
      start,
      end,
      lineStart: { x: start.x + nx * offset, y: start.y + ny * offset },
      lineEnd: { x: end.x + nx * offset, y: end.y + ny * offset },
      label,
    };
  }

  function drawDimension(ctx, item, selected = false) {
    const color = '#000000';
    const line = getDimensionLine(item);
    ctx.save();
    ctx.strokeStyle = color;
    ctx.fillStyle = color;
    const dimensionLineWidth = selected ? 3 : 2.25;
    ctx.lineWidth = dimensionLineWidth;

    [item.start, item.end].forEach((point, index) => {
      const target = index === 0 ? line.start : line.end;
      ctx.beginPath();
      ctx.moveTo(point.x, point.y);
      ctx.lineTo(target.x, target.y);
      ctx.stroke();
    });

    ctx.beginPath();
    ctx.moveTo(line.start.x, line.start.y);
    ctx.lineTo(line.end.x, line.end.y);
    ctx.stroke();

    const tickLength = 18;
    const tickAngle = (135 * Math.PI) / 180;
    ctx.lineWidth = dimensionLineWidth * 2;
    [line.start, line.end].forEach((point) => {
      ctx.beginPath();
      ctx.moveTo(point.x - Math.cos(tickAngle) * tickLength / 2, point.y - Math.sin(tickAngle) * tickLength / 2);
      ctx.lineTo(point.x + Math.cos(tickAngle) * tickLength / 2, point.y + Math.sin(tickAngle) * tickLength / 2);
      ctx.stroke();
    });
    ctx.lineWidth = dimensionLineWidth;

    const midX = (line.start.x + line.end.x) / 2;
    const midY = (line.start.y + line.end.y) / 2;
    ctx.font = '700 21px Helvetica, Arial, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    const label = item.label || 'Dimension';
    const width = ctx.measureText(label).width;
    const labelPaddingX = 10;
    const labelHeight = 30;
    const labelWidth = width + labelPaddingX * 2;
    const dx = line.end.x - line.start.x;
    const dy = line.end.y - line.start.y;
    const lineLength = Math.hypot(dx, dy);
    const isMostlyHorizontal = Math.abs(dx) >= Math.abs(dy);
    const needsOutsideLabel = isMostlyHorizontal ? lineLength < labelWidth + 28 : lineLength < labelHeight + 28;
    let labelX = midX;
    let labelY = midY;

    if (needsOutsideLabel) {
      const leaderGap = 8;
      if (isMostlyHorizontal) {
        const leftPoint = line.start.x <= line.end.x ? line.start : line.end;
        const rightPoint = line.start.x <= line.end.x ? line.end : line.start;
        const placeLeft = leftPoint.x >= labelWidth + leaderGap || leftPoint.x >= ctx.canvas.width - rightPoint.x;
        labelX = placeLeft ? leftPoint.x - leaderGap - labelWidth / 2 : rightPoint.x + leaderGap + labelWidth / 2;
        labelY = midY;
        ctx.beginPath();
        ctx.moveTo(placeLeft ? leftPoint.x : rightPoint.x, midY);
        ctx.lineTo(placeLeft ? labelX + labelWidth / 2 : labelX - labelWidth / 2, labelY);
        ctx.stroke();
      } else {
        const topPoint = line.start.y <= line.end.y ? line.start : line.end;
        const bottomPoint = line.start.y <= line.end.y ? line.end : line.start;
        const placeTop = topPoint.y >= labelHeight + leaderGap || topPoint.y >= ctx.canvas.height - bottomPoint.y;
        labelX = midX;
        labelY = placeTop ? topPoint.y - leaderGap - labelHeight / 2 : bottomPoint.y + leaderGap + labelHeight / 2;
        ctx.beginPath();
        ctx.moveTo(midX, placeTop ? topPoint.y : bottomPoint.y);
        ctx.lineTo(labelX, placeTop ? labelY + labelHeight / 2 : labelY - labelHeight / 2);
        ctx.stroke();
      }
    }

    ctx.fillStyle = '#ffffff';
    ctx.fillRect(labelX - labelWidth / 2, labelY - labelHeight / 2, labelWidth, labelHeight);
    ctx.fillStyle = color;
    ctx.fillText(label, labelX, labelY);
    ctx.restore();
  }

  function renderCanvas(showGuides) {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if (baseImage) {
      ctx.drawImage(baseImage, 0, 0, canvas.width, canvas.height);
    } else {
      ctx.fillStyle = '#e5e5e5';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }

    artworks.forEach((item) => {
      const alpha = item.opacity == null ? 1 : item.opacity;
      ctx.save();
      ctx.globalAlpha = alpha;
      drawImageInQuad(ctx, item.img, item.points);
      ctx.restore();
      if (showGuides && item.id === selectedArtworkId) {
        const rotateHandle = getRotateHandle(item);
        ctx.strokeStyle = '#f5a523';
        ctx.lineWidth = 2;
        ctx.setLineDash([6, 4]);
        ctx.beginPath();
        item.points.forEach((point, index) => {
          if (index === 0) ctx.moveTo(point.x, point.y);
          else ctx.lineTo(point.x, point.y);
        });
        ctx.closePath();
        ctx.stroke();
        ctx.setLineDash([]);
        ctx.beginPath();
        const topMid = { x: (item.points[0].x + item.points[1].x) / 2, y: (item.points[0].y + item.points[1].y) / 2 };
        ctx.moveTo(topMid.x, topMid.y);
        ctx.lineTo(rotateHandle.x, rotateHandle.y);
        ctx.stroke();
        getArtworkHandles(item).forEach(({ x, y }) => {
          ctx.fillStyle = '#ffffff';
          ctx.strokeStyle = '#f5a523';
          ctx.lineWidth = 2;
          ctx.beginPath();
          ctx.rect(x - HANDLE_SIZE / 2, y - HANDLE_SIZE / 2, HANDLE_SIZE, HANDLE_SIZE);
          ctx.fill();
          ctx.stroke();
        });
        ctx.fillStyle = '#1a1a1a';
        ctx.strokeStyle = '#f5a523';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(rotateHandle.x, rotateHandle.y, HANDLE_SIZE / 1.5, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();
        ctx.fillStyle = '#f5a523';
        ctx.font = '700 10px Helvetica, Arial, sans-serif';
        ctx.fillText('↻', rotateHandle.x - 4, rotateHandle.y + 4);
      }
    });

    dimensions.forEach(item => drawDimension(ctx, item, item.id === activeDimensionId));
    if (showGuides && draftPoints.length > 0) {
      ctx.fillStyle = '#000000';
      draftPoints.forEach((point) => {
        ctx.beginPath();
        ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
        ctx.fill();
      });
    }
    if (showGuides && draftPoints.length === 2 && draftOffsetPoint) {
      drawDimension(ctx, buildOffsetDimension(draftPoints[0], draftPoints[1], draftOffsetPoint, dimensionText || 'Dimension'), true);
    }
    if (showGuides && boxStart && boxEnd) {
      const rect = getRectFromPoints(boxStart, boxEnd);
      ctx.save();
      ctx.strokeStyle = '#000000';
      ctx.lineWidth = 2;
      ctx.setLineDash([6, 4]);
      ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
      ctx.setLineDash([]);
      ctx.restore();
    }

    if (showGuides && calibrationPoints.length > 0) {
      ctx.fillStyle = '#22c55e';
      calibrationPoints.forEach((point) => {
        ctx.beginPath();
        ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
        ctx.fill();
      });
      if (calibrationPoints.length === 2) {
        ctx.strokeStyle = '#22c55e';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(calibrationPoints[0].x, calibrationPoints[0].y);
        ctx.lineTo(calibrationPoints[1].x, calibrationPoints[1].y);
        ctx.stroke();
      }
    }
  }

  const hitHandle = (point, item = selectedArtwork) => {
    if (!item) return null;
    return getArtworkHandles(item).find(handle =>
      Math.abs(point.x - handle.x) <= HANDLE_SIZE && Math.abs(point.y - handle.y) <= HANDLE_SIZE
    ) || null;
  };

  const hitRotateHandle = (point, item = selectedArtwork) => {
    if (!item) return false;
    const handle = getRotateHandle(item);
    return Math.hypot(point.x - handle.x, point.y - handle.y) <= HANDLE_SIZE * 1.4;
  };

  const findArtworkAtPoint = (point) => {
    for (let i = artworks.length - 1; i >= 0; i -= 1) {
      if (pointInPolygon(point, artworks[i].points)) return artworks[i];
    }
    return null;
  };

  const pxPerInch = (() => {
    if (calibrationPoints.length !== 2 || !Number(calibrationDistance)) return null;
    const [a, b] = calibrationPoints;
    const px = Math.hypot(b.x - a.x, b.y - a.y);
    return px / Number(calibrationDistance);
  })();

  function getRectFromPoints(a, b) {
    const x = Math.min(a.x, b.x);
    const y = Math.min(a.y, b.y);
    return { x, y, w: Math.abs(b.x - a.x), h: Math.abs(b.y - a.y) };
  }

  function addBoxDimensions(rect) {
    const offset = 28;
    const groupId = crypto.randomUUID();
    const widthDim = {
      ...buildOffsetDimension(
        { x: rect.x, y: rect.y },
        { x: rect.x + rect.w, y: rect.y },
        { x: rect.x + rect.w / 2, y: rect.y - offset },
        boxWidthText || dimensionText || 'Width'
      ),
      group: 'box',
      groupId,
    };
    const heightDim = {
      ...buildOffsetDimension(
        { x: rect.x, y: rect.y },
        { x: rect.x, y: rect.y + rect.h },
        { x: rect.x - offset, y: rect.y + rect.h / 2 },
        boxHeightText || dimensionText || 'Height'
      ),
      group: 'box',
      groupId,
    };
    setDimensions(prev => [...prev, widthDim, heightDim]);
    setActiveDimensionId(widthDim.id);
    setSelectedArtworkId(null);
  }

  const autoDimensionSelectedArtwork = () => {
    if (!selectedArtwork) return;
    const bounds = getArtworkBounds(selectedArtwork.points);
    if (bounds.w < 8 || bounds.h < 8) return;
    addBoxDimensions(bounds);
  };

  const findDimensionAtPoint = (point) => {
    const distanceToSegment = (pointValue, start, end) => {
      const dx = end.x - start.x;
      const dy = end.y - start.y;
      const lenSq = dx * dx + dy * dy || 1;
      const t = Math.max(0, Math.min(1, ((pointValue.x - start.x) * dx + (pointValue.y - start.y) * dy) / lenSq));
      const x = start.x + t * dx;
      const y = start.y + t * dy;
      return Math.hypot(pointValue.x - x, pointValue.y - y);
    };
    return dimensions.find(item => {
      const line = getDimensionLine(item);
      return distanceToSegment(point, line.start, line.end) < 12;
    }) || null;
  };

  const updateActiveDimensionLabel = () => {
    if (!activeDimensionId) return;
    setDimensions(prev => prev.map(item => item.id === activeDimensionId ? { ...item, label: dimensionText || 'Dimension' } : item));
  };

  const removeActiveDimension = () => {
    if (!activeDimensionId) return;
    const selected = dimensions.find(item => item.id === activeDimensionId);
    setDimensions(prev => {
      if (selected?.group === 'box' && selected.groupId) return prev.filter(item => item.groupId !== selected.groupId);
      return prev.filter(item => item.id !== activeDimensionId);
    });
    setActiveDimensionId(null);
    setDimensionText('');
  };

  const resetDimensionDraft = () => {
    setDraftPoints([]);
    setDraftOffsetPoint(null);
    setIsPlacingOffset(false);
    setBoxStart(null);
    setBoxEnd(null);
  };

  const autoDetectBoxDimensions = () => {
    if (!baseImage) return;
    const canvas = canvasRef.current;
    if (!canvas) return;
    const detectCanvas = document.createElement('canvas');
    const maxDetectW = 300;
    const scale = Math.min(maxDetectW / canvas.width, 1);
    detectCanvas.width = Math.max(1, Math.round(canvas.width * scale));
    detectCanvas.height = Math.max(1, Math.round(canvas.height * scale));
    const detectCtx = detectCanvas.getContext('2d', { willReadFrequently: true });
    detectCtx.drawImage(baseImage, 0, 0, detectCanvas.width, detectCanvas.height);
    const { width, height } = detectCanvas;
    const { data } = detectCtx.getImageData(0, 0, width, height);
    const columnCounts = new Uint16Array(width);
    const rowCounts = new Uint16Array(height);
    const marginX = Math.round(width * 0.01);
    const marginY = Math.round(height * 0.01);
    let nonWhiteCount = 0;

    for (let y = marginY; y < height - marginY; y++) {
      for (let x = marginX; x < width - marginX; x++) {
        const p = (y * width + x) * 4;
        const isWhite = data[p] >= 245 && data[p + 1] >= 245 && data[p + 2] >= 245;
        if (data[p + 3] > 24 && !isWhite) {
          columnCounts[x] += 1;
          rowCounts[y] += 1;
          nonWhiteCount += 1;
        }
      }
    }

    const minColumnHits = Math.max(2, Math.round(height * 0.006));
    const minRowHits = Math.max(2, Math.round(width * 0.006));
    let minX = -1;
    let maxX = -1;
    let minY = -1;
    let maxY = -1;
    for (let x = marginX; x < width - marginX; x++) if (columnCounts[x] >= minColumnHits) { minX = x; break; }
    for (let x = width - marginX - 1; x >= marginX; x--) if (columnCounts[x] >= minColumnHits) { maxX = x; break; }
    for (let y = marginY; y < height - marginY; y++) if (rowCounts[y] >= minRowHits) { minY = y; break; }
    for (let y = height - marginY - 1; y >= marginY; y--) if (rowCounts[y] >= minRowHits) { maxY = y; break; }

    const boundsW = maxX - minX;
    const boundsH = maxY - minY;
    const nonWhiteRatio = nonWhiteCount / ((width - marginX * 2) * (height - marginY * 2));
    if (minX < 0 || minY < 0 || boundsW < width * 0.03 || boundsH < height * 0.03 || nonWhiteRatio > 0.65) {
      window.alert('Could not detect a clear non-white object. Drag a box manually.');
      return;
    }

    const padding = 4;
    const rectX = Math.max(0, minX / scale - padding);
    const rectY = Math.max(0, minY / scale - padding);
    addBoxDimensions({
      x: rectX,
      y: rectY,
      w: Math.min(canvas.width, maxX / scale + padding) - rectX,
      h: Math.min(canvas.height, maxY / scale + padding) - rectY,
    });
  };

  const handleArtworkFiles = (fileList) => {
    const files = Array.from(fileList || []).filter(isPhotoFile);
    if (!files.length) {
      alert('Please upload image artwork.');
      return;
    }
    files.forEach((file, fileIndex) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      URL.revokeObjectURL(url);
      const canvas = canvasRef.current;
      const maxW = canvas.width * 0.35;
      const maxH = canvas.height * 0.35;
      const scale = Math.min(maxW / img.naturalWidth, maxH / img.naturalHeight, 1);
      const w = Math.max(40, img.naturalWidth * scale);
      const h = Math.max(40, img.naturalHeight * scale);
      const id = crypto.randomUUID();
      const offset = (artworks.length + fileIndex) * 18;
      const x = (canvas.width - w) / 2 + offset;
      const y = (canvas.height - h) / 2 + offset;
      const nextArtwork = {
        id,
        name: file.name,
        img,
        points: makeArtworkPoints(x, y, w, h),
        ratio: img.naturalWidth / img.naturalHeight,
        opacity: 1,
      };
      commitArtworks(prev => [...prev, nextArtwork]);
      setSelectedArtworkId(id);
      setArtworkWidth('');
      setArtworkHeight('');
    };
    img.onerror = () => {
      URL.revokeObjectURL(url);
      alert('Could not load that artwork file.');
    };
    img.src = url;
    });
  };

  const applySpecifiedSize = () => {
    if (!selectedArtwork) return;
    const widthValue = Number(artworkWidth);
    const heightValue = Number(artworkHeight);
    if (!widthValue && !heightValue) return;
    if (sizeUnit === 'in' && !pxPerInch) {
      alert('Calibrate the photo first, or switch the size unit to pixels.');
      return;
    }
    const multiplier = sizeUnit === 'in' ? pxPerInch : 1;
    const bounds = getArtworkBounds(selectedArtwork.points);
    let nextW = widthValue ? widthValue * multiplier : bounds.w;
    let nextH = heightValue ? heightValue * multiplier : bounds.h;
    if (lockRatio && widthValue && !heightValue) nextH = nextW / selectedArtwork.ratio;
    if (lockRatio && heightValue && !widthValue) nextW = nextH * selectedArtwork.ratio;
    if (lockRatio && widthValue && heightValue) nextH = nextW / selectedArtwork.ratio;
    const centerX = bounds.x + bounds.w / 2;
    const centerY = bounds.y + bounds.h / 2;
    commitArtworks(prev => prev.map(item => item.id === selectedArtwork.id
      ? { ...item, points: makeArtworkPoints(centerX - nextW / 2, centerY - nextH / 2, Math.max(10, nextW), Math.max(10, nextH)) }
      : item
    ));
  };

  const removeSelectedArtwork = () => {
    if (!selectedArtworkId) return;
    commitArtworks(prev => {
      const next = prev.filter(item => item.id !== selectedArtworkId);
      setSelectedArtworkId(next[next.length - 1]?.id || null);
      return next;
    });
  };

  const duplicateSelectedArtwork = () => {
    if (!selectedArtwork) return;
    const id = crypto.randomUUID();
    const copy = {
      ...selectedArtwork,
      id,
      name: `${selectedArtwork.name || 'Artwork'} copy`,
      points: selectedArtwork.points.map(point => ({ x: point.x + 18, y: point.y + 18 })),
    };
    commitArtworks(prev => [...prev, copy]);
    setSelectedArtworkId(id);
  };

  const reorderLayer = (dragId, targetId) => {
    if (!dragId || !targetId || dragId === targetId) return;
    commitArtworks(prev => {
      const next = [...prev];
      const from = next.findIndex(item => item.id === dragId);
      const to = next.findIndex(item => item.id === targetId);
      if (from < 0 || to < 0) return prev;
      const [moved] = next.splice(from, 1);
      next.splice(to, 0, moved);
      return next;
    });
  };

  const updateSelectedArtwork = (changes) => {
    if (!selectedArtworkId) return;
    commitArtworks(prev => prev.map(item => item.id === selectedArtworkId ? { ...item, ...changes } : item));
  };

  const nudgeSelectedArtwork = (dx, dy) => {
    if (!selectedArtworkId) return;
    commitArtworks(prev => prev.map(item => item.id === selectedArtworkId
      ? { ...item, points: item.points.map(point => ({ x: point.x + dx, y: point.y + dy })) }
      : item
    ));
  };

  const undo = () => {
    if (historyIndex <= 0) return;
    const nextIndex = historyIndex - 1;
    const next = cloneArtworkState(history[nextIndex] || []);
    setHistoryIndex(nextIndex);
    setArtworks(next);
    setSelectedArtworkId(current => next.some(item => item.id === current) ? current : next[next.length - 1]?.id || null);
  };

  const redo = () => {
    if (historyIndex >= history.length - 1) return;
    const nextIndex = historyIndex + 1;
    const next = cloneArtworkState(history[nextIndex] || []);
    setHistoryIndex(nextIndex);
    setArtworks(next);
    setSelectedArtworkId(current => next.some(item => item.id === current) ? current : next[next.length - 1]?.id || null);
  };

  const handlePointerDown = (e) => {
    e.preventDefault();
    const point = getCanvasPos(e);
    if (activeTool === 'box') {
      setBoxStart(point);
      setBoxEnd(point);
      setActiveDimensionId(null);
      setSelectedArtworkId(null);
      return;
    }
    if (activeTool === 'dimension') {
      if (draftPoints.length === 2) {
        setIsPlacingOffset(true);
        setDraftOffsetPoint(point);
        return;
      }
      const existingDimension = findDimensionAtPoint(point);
      if (existingDimension) {
        setActiveDimensionId(existingDimension.id);
        setSelectedArtworkId(null);
        setDimensionText(existingDimension.label || '');
        return;
      }
      setDraftPoints(prev => prev.length >= 2 ? [point] : [...prev, point]);
      setDraftOffsetPoint(null);
      setActiveDimensionId(null);
      setSelectedArtworkId(null);
      return;
    }
    if (calibrating) {
      setCalibrationPoints(prev => {
        const next = prev.length >= 2 ? [point] : [...prev, point];
        if (next.length === 2) setCalibrating(false);
        return next;
      });
      return;
    }
    const existingDimension = findDimensionAtPoint(point);
    if (existingDimension && !findArtworkAtPoint(point)) {
      setActiveDimensionId(existingDimension.id);
      setSelectedArtworkId(null);
      setDimensionText(existingDimension.label || '');
      return;
    }
    const targetArtwork = selectedArtwork && (hitHandle(point, selectedArtwork) || pointInPolygon(point, selectedArtwork.points))
      ? selectedArtwork
      : findArtworkAtPoint(point);
    if (!targetArtwork) {
      setSelectedArtworkId(null);
      setActiveDimensionId(null);
      return;
    }
    setSelectedArtworkId(targetArtwork.id);
    setActiveDimensionId(null);
    const handle = hitHandle(point, targetArtwork);
    if (hitRotateHandle(point, targetArtwork)) {
      const center = targetArtwork.points.reduce((acc, p) => ({ x: acc.x + p.x / targetArtwork.points.length, y: acc.y + p.y / targetArtwork.points.length }), { x: 0, y: 0 });
      dragRef.current = {
        type: 'rotate',
        startAngle: Math.atan2(point.y - center.y, point.x - center.x),
        center,
        initial: { ...targetArtwork, points: targetArtwork.points.map(p => ({ ...p })) },
      };
    } else if (handle) {
      dragRef.current = { type: 'resize', handle, start: point, initial: { ...targetArtwork, points: targetArtwork.points.map(p => ({ ...p })) } };
    } else {
      dragRef.current = { type: 'move', start: point, initial: { ...targetArtwork, points: targetArtwork.points.map(p => ({ ...p })) } };
    }
  };

  const handlePointerMove = (e) => {
    if (activeTool === 'box' && boxStart) {
      e.preventDefault();
      setBoxEnd(getCanvasPos(e));
      return;
    }
    if (activeTool === 'dimension' && isPlacingOffset && draftPoints.length === 2) {
      e.preventDefault();
      setDraftOffsetPoint(getCanvasPos(e));
      return;
    }
    if (!dragRef.current) return;
    e.preventDefault();
    const point = getCanvasPos(e);
    const drag = dragRef.current;
    if (drag.type === 'move') {
      const dx = point.x - drag.start.x;
      const dy = point.y - drag.start.y;
      const points = drag.initial.points.map(p => ({ x: p.x + dx, y: p.y + dy }));
      setArtworksLive(prev => prev.map(item => item.id === drag.initial.id ? { ...item, points } : item));
      return;
    }

    if (drag.type === 'rotate') {
      const currentAngle = Math.atan2(point.y - drag.center.y, point.x - drag.center.x);
      const degrees = ((currentAngle - drag.startAngle) * 180) / Math.PI;
      const points = rotatePoints(drag.initial.points, degrees);
      setArtworksLive(prev => prev.map(item => item.id === drag.initial.id ? { ...item, points } : item));
      return;
    }

    const minSize = 16;
    const handleIndex = drag.handle.index;
    if (!lockRatio) {
      const points = drag.initial.points.map((p, index) => index === handleIndex ? point : { ...p });
      setArtworksLive(prev => prev.map(item => item.id === drag.initial.id ? { ...item, points } : item));
      return;
    }

    const oppositeIndex = (handleIndex + 2) % 4;
    const anchor = drag.initial.points[oppositeIndex];
    const dx = point.x - anchor.x;
    const dy = point.y - anchor.y;
    let w = Math.max(minSize, Math.abs(dx));
    let h = w / drag.initial.ratio;
    if (Math.abs(dy) > h) {
      h = Math.max(minSize, Math.abs(dy));
      w = h * drag.initial.ratio;
    }
    const x = dx >= 0 ? anchor.x : anchor.x - w;
    const y = dy >= 0 ? anchor.y : anchor.y - h;
    const points = makeArtworkPoints(x, y, w, h);
    setArtworksLive(prev => prev.map(item => item.id === drag.initial.id ? { ...item, points } : item));
  };

  const handlePointerUp = () => {
    if (activeTool === 'box') {
      if (!boxStart || !boxEnd) return;
      const rect = getRectFromPoints(boxStart, boxEnd);
      if (rect.w >= 8 && rect.h >= 8) addBoxDimensions(rect);
      setBoxStart(null);
      setBoxEnd(null);
      return;
    }
    if (activeTool === 'dimension') {
      if (!isPlacingOffset || draftPoints.length !== 2 || !draftOffsetPoint) return;
      if (Math.hypot(draftPoints[1].x - draftPoints[0].x, draftPoints[1].y - draftPoints[0].y) >= 8) {
        const next = buildOffsetDimension(draftPoints[0], draftPoints[1], draftOffsetPoint, dimensionText || 'Dimension');
        setDimensions(prev => [...prev, next]);
        setActiveDimensionId(next.id);
      }
      setDraftPoints([]);
      setDraftOffsetPoint(null);
      setIsPlacingOffset(false);
      return;
    }
    if (dragRef.current) {
      const draggedId = dragRef.current.initial.id;
      setArtworks(prev => {
        setHistory(current => {
          const trimmed = current.slice(0, historyIndex + 1);
          return [...trimmed, cloneArtworkState(prev)].slice(-40);
        });
        setHistoryIndex(index => Math.min(index + 1, 39));
        setSelectedArtworkId(draggedId);
        return prev;
      });
    }
    dragRef.current = null;
  };

  const handleArtworkDrop = (e) => {
    e.preventDefault();
    handleArtworkFiles(e.dataTransfer.files);
  };

  const handleApply = () => {
    renderCanvas(false);
    canvasRef.current.toBlob(blob => {
      if (!blob) return;
      const file = new File([blob], effectiveOutputName, { type: 'image/jpeg' });
      if (activeTarget) {
        setTargetSources(prev => ({ ...prev, [activeTarget.id]: URL.createObjectURL(blob) }));
        onApply(file, activeTarget.id);
      } else {
        onApply(file);
      }
    }, 'image/jpeg', 0.92);
    requestAnimationFrame(() => renderCanvas(true));
  };

  const saveAsJpg = () => {
    renderCanvas(false);
    canvasRef.current.toBlob(blob => {
      if (!blob) return;
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `${effectiveDownloadPrefix}-${new Date().toISOString().slice(0, 10)}.jpg`;
      a.click();
      URL.revokeObjectURL(url);
      requestAnimationFrame(() => renderCanvas(true));
    }, 'image/jpeg', 0.92);
  };

  useEffect(() => {
    const handleKeyDown = (event) => {
      if (!selectedArtworkId) return;
      const target = event.target;
      if (target && ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) return;
      const step = event.shiftKey ? 10 : 1;
      if (event.key === 'ArrowUp') { event.preventDefault(); nudgeSelectedArtwork(0, -step); }
      if (event.key === 'ArrowDown') { event.preventDefault(); nudgeSelectedArtwork(0, step); }
      if (event.key === 'ArrowLeft') { event.preventDefault(); nudgeSelectedArtwork(-step, 0); }
      if (event.key === 'ArrowRight') { event.preventDefault(); nudgeSelectedArtwork(step, 0); }
      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
        event.preventDefault();
        if (event.shiftKey) redo();
        else undo();
      }
      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'y') {
        event.preventDefault();
        redo();
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedArtworkId, historyIndex, history]);

  return (
    <div
      style={{ ...popupOverlayStyle, zIndex: 9999, padding: 20 }}
      onClick={(e) => { if (e.target === e.currentTarget) onCancel(); }}
    >
      <div style={{ ...popupSurfaceStyle, borderRadius: 8, display: 'flex', flexDirection: 'column', maxWidth: '98vw', maxHeight: '96vh', overflow: 'hidden', padding: 0 }}>
        {/* Header */}
        <div style={{ padding: '12px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 400, color: 'var(--text-primary)' }}>{title}</div>
            <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{subtitle}</div>
          </div>
          <button onClick={onCancel} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: 18, lineHeight: 1, padding: '0 2px' }}>✕</button>
        </div>

        {targets && (
          <div style={{ padding: '10px 18px 0', display: 'flex', gap: 8, flexShrink: 0 }}>
            {targets.map(target => (
              <button
                key={target.id}
                type="button"
                className={`btn btn-sm ${target.id === activeTargetId ? 'btn-primary' : 'btn-outline'}`}
                onClick={() => setActiveTargetId(target.id)}
              >
                {target.label}
              </button>
            ))}
            <span style={{ alignSelf: 'center', fontSize: 11, color: 'var(--text-muted)' }}>
              Apply saves the current tab, then you can switch tabs without closing.
            </span>
          </div>
        )}

        {/* Toolbar */}
        <div style={{ padding: '10px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', background: 'var(--card-bg-2, var(--card-bg))', flexShrink: 0 }}>
          <div style={{ display: 'flex', border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden' }}>
            {[
              ['select', 'Select'],
              ['dimension', 'Line Dim'],
              ['box', 'Box Dim'],
            ].map(([tool, label]) => (
              <button
                key={tool}
                className={`btn btn-sm ${activeTool === tool ? 'btn-primary' : 'btn-outline'}`}
                style={{ border: 'none', borderRadius: 0 }}
                onClick={() => { setActiveTool(tool); resetDimensionDraft(); setCalibrating(false); }}
              >
                {label}
              </button>
            ))}
          </div>
          <button className="btn btn-primary btn-sm" onClick={() => artworkInputRef.current?.click()}>Import Artwork</button>
          <input
            ref={artworkInputRef}
            type="file"
            accept={PHOTO_FILE_ACCEPT}
            multiple
            style={{ display: 'none' }}
            onChange={e => { if (e.target.files.length) handleArtworkFiles(e.target.files); e.target.value = ''; }}
          />
          <button className="btn btn-outline btn-sm" onClick={undo} disabled={historyIndex <= 0}>Undo</button>
          <button className="btn btn-outline btn-sm" onClick={redo} disabled={historyIndex >= history.length - 1}>Redo</button>
          <button className="btn btn-outline btn-sm" onClick={removeSelectedArtwork} disabled={!selectedArtwork}>Remove Artwork</button>
          <button className="btn btn-outline btn-sm" onClick={duplicateSelectedArtwork} disabled={!selectedArtwork}>Duplicate</button>
          <button className="btn btn-outline btn-sm" onClick={autoDimensionSelectedArtwork} disabled={!selectedArtwork}>Auto Dim Artwork</button>
          <input
            type="text"
            value={dimensionText}
            onChange={e => setDimensionText(e.target.value)}
            placeholder='Dimension text'
            style={{ minHeight: 32, margin: 0, width: 145 }}
          />
          {activeTool === 'box' && (
            <>
              <input
                type="text"
                value={boxWidthText}
                onChange={e => setBoxWidthText(e.target.value)}
                placeholder='Width'
                style={{ minHeight: 32, margin: 0, width: 90 }}
              />
              <input
                type="text"
                value={boxHeightText}
                onChange={e => setBoxHeightText(e.target.value)}
                placeholder='Height'
                style={{ minHeight: 32, margin: 0, width: 90 }}
              />
              <button className="btn btn-outline btn-sm" onClick={autoDetectBoxDimensions} disabled={!loaded || !baseImage}>Auto Box</button>
            </>
          )}
          <button className="btn btn-outline btn-sm" onClick={updateActiveDimensionLabel} disabled={!activeDimension}>Update Dim</button>
          <button className="btn btn-outline btn-sm" onClick={removeActiveDimension} disabled={!activeDimension}>Remove Dim</button>
          <button className={`btn btn-sm ${calibrating ? 'btn-primary' : 'btn-outline'}`} onClick={() => { setCalibrating(true); setCalibrationPoints([]); }}>
            Pick Scale Points
          </button>
          <input
            type="number"
            min="0"
            step="0.01"
            placeholder="Real distance"
            value={calibrationDistance}
            onChange={e => setCalibrationDistance(e.target.value)}
            style={{ width: 110, minHeight: 32, margin: 0 }}
          />
          <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>inches</span>
          <div style={{ width: 1, height: 28, background: 'var(--border)' }} />
          <input
            type="number"
            min="0"
            step="0.01"
            placeholder="Artwork W"
            value={artworkWidth}
            onChange={e => setArtworkWidth(e.target.value)}
            style={{ width: 100, minHeight: 32, margin: 0 }}
          />
          <input
            type="number"
            min="0"
            step="0.01"
            placeholder="Artwork H"
            value={artworkHeight}
            onChange={e => setArtworkHeight(e.target.value)}
            style={{ width: 100, minHeight: 32, margin: 0 }}
          />
          <select value={sizeUnit} onChange={e => setSizeUnit(e.target.value)} style={{ width: 72, minHeight: 32, margin: 0 }}>
            <option value="in">in</option>
            <option value="px">px</option>
          </select>
          <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-muted)' }}>
            <input type="checkbox" checked={lockRatio} onChange={e => setLockRatio(e.target.checked)} style={{ width: 14, height: 14, margin: 0 }} />
            Lock ratio
          </label>
          <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-muted)' }}>
            Opacity
            <input
              type="range"
              min="0.1"
              max="1"
              step="0.05"
              value={selectedArtwork?.opacity ?? 1}
              onChange={e => updateSelectedArtwork({ opacity: Number(e.target.value) })}
              disabled={!selectedArtwork}
              style={{ width: 90 }}
            />
          </label>
          <button className="btn btn-outline btn-sm" onClick={applySpecifiedSize} disabled={!selectedArtwork}>Apply Size</button>
          <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
            {pxPerInch ? `${pxPerInch.toFixed(1)} px/in` : calibrating ? 'Click two points on a known-size item' : 'Drop artwork here, drag layers, lock ratio for normal resize, unlock for free transform'}
          </span>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '220px minmax(0, 1fr)', minHeight: 0, flex: 1 }}>
          <div style={{ borderRight: '1px solid var(--border)', background: 'var(--card-bg)', padding: 12, overflowY: 'auto' }}>
            <div style={{ fontSize: 11, fontWeight: 400, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 10 }}>Layers</div>
            {dimensions.length > 0 && (
              <>
                <div style={{ fontSize: 10, fontWeight: 400, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 6 }}>Dimensions</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 14 }}>
                  {dimensions.map((item, index) => (
                    <button
                      key={item.id}
                      type="button"
                      onClick={() => {
                        setActiveDimensionId(item.id);
                        setSelectedArtworkId(null);
                        setDimensionText(item.label || '');
                        setActiveTool('select');
                      }}
                      style={{
                        border: `1px solid ${item.id === activeDimensionId ? 'var(--accent)' : 'var(--border)'}`,
                        background: item.id === activeDimensionId ? 'rgba(245,165,35,0.12)' : 'var(--card-bg-2)',
                        color: 'var(--text-primary)',
                        borderRadius: 4,
                        padding: '8px 10px',
                        cursor: 'pointer',
                        textAlign: 'left',
                        fontSize: 12,
                      }}
                    >
                      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
                        <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.label || 'Dimension'}</span>
                        <span style={{ color: 'var(--text-muted)', fontSize: 10 }}>D{index + 1}</span>
                      </div>
                    </button>
                  ))}
                </div>
              </>
            )}
            <div style={{ fontSize: 10, fontWeight: 400, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 6 }}>Artwork</div>
            {artworks.length === 0 ? (
              <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
                Import or drop artwork to create layers.
              </div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {[...artworks].reverse().map((item) => {
                  const displayIndex = artworks.findIndex(layer => layer.id === item.id) + 1;
                  return (
                    <button
                      key={item.id}
                      type="button"
                      draggable
                      onDragStart={() => { layerDragRef.current = item.id; }}
                      onDragOver={e => e.preventDefault()}
                      onDrop={() => {
                        reorderLayer(layerDragRef.current, item.id);
                        layerDragRef.current = null;
                      }}
                      onClick={() => {
                        setSelectedArtworkId(item.id);
                        setActiveDimensionId(null);
                      }}
                      style={{
                        border: `1px solid ${item.id === selectedArtworkId ? 'var(--accent)' : 'var(--border)'}`,
                        background: item.id === selectedArtworkId ? 'rgba(245,165,35,0.12)' : 'var(--card-bg-2)',
                        color: 'var(--text-primary)',
                        borderRadius: 4,
                        padding: '8px 10px',
                        cursor: 'grab',
                        textAlign: 'left',
                        fontSize: 12,
                      }}
                    >
                      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
                        <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.name || 'Artwork'}</span>
                        <span style={{ color: 'var(--text-muted)', fontSize: 10 }}>#{displayIndex}</span>
                      </div>
                    </button>
                  );
                })}
              </div>
            )}
            <div style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.5, marginTop: 12 }}>
              Drag artwork layers up/down to change stacking. Use arrow keys to nudge selected artwork.
            </div>
          </div>

          {/* Canvas area */}
          <div
            onDragOver={e => e.preventDefault()}
            onDrop={handleArtworkDrop}
            style={{ overflow: 'auto', background: '#2a2a2a', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', position: 'relative', minHeight: 420 }}
          >
            {!loaded && (
              <div style={{ padding: 48, color: '#888', fontSize: 13 }}>Loading image…</div>
            )}
            <canvas
              ref={canvasRef}
              style={{ display: loaded ? 'block' : 'none', cursor: calibrating || activeTool !== 'select' ? 'crosshair' : 'move', maxWidth: '100%', touchAction: 'none', userSelect: 'none' }}
              onPointerDown={handlePointerDown}
              onPointerMove={handlePointerMove}
              onPointerUp={handlePointerUp}
              onPointerLeave={handlePointerUp}
            />
          </div>
        </div>

        {/* Footer */}
        <div style={{ padding: '12px 18px', borderTop: '1px solid var(--border)', display: 'flex', justifyContent: 'flex-end', gap: 8, flexShrink: 0 }}>
          <button className="btn btn-outline btn-sm" onClick={onCancel}>Cancel</button>
          <button className="btn btn-outline btn-sm" onClick={saveAsJpg}>Save as JPG</button>
          <button className="btn btn-primary btn-sm" onClick={handleApply}>{effectiveApplyLabel}</button>
        </div>
      </div>
    </div>
  );
}
