'use client';

import { useRef, useState, useEffect, useLayoutEffect, useCallback } from 'react';
import { Stage, Layer, Text, Image as KonvaImage, Transformer, Rect } from 'react-konva';
import type Konva from 'konva';
import { v4 as uuid } from 'uuid';
import ProductMockup from './ProductMockup';
import Price from './Price';
import LoginPromptModal from './LoginPromptModal';
import { useCart } from '@/lib/cartStore';
import { getEffectivePrice } from '@/lib/pricing';
import { checkCustomerAuth } from '@/app/actions';
import type { DesignElement, PrintArea, ProductDTO } from '@/lib/types';

const DISPLAY = 560; // css px size the mockup + stage are rendered at
const CAPTURE_SCALE = 2; // matches pixelRatio used for exports, for crisp downloads

function loadImage(src: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const img = new window.Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

// Draws `img` into the canvas region like CSS `object-fit: cover` would.
function drawCover(ctx: CanvasRenderingContext2D, img: HTMLImageElement, dw: number, dh: number) {
  const imgRatio = img.width / img.height;
  const boxRatio = dw / dh;
  let sx: number, sy: number, sw: number, sh: number;
  if (imgRatio > boxRatio) {
    sh = img.height;
    sw = sh * boxRatio;
    sx = (img.width - sw) / 2;
    sy = 0;
  } else {
    sw = img.width;
    sh = sw / boxRatio;
    sx = 0;
    sy = (img.height - sh) / 2;
  }
  ctx.drawImage(img, sx, sy, sw, sh, 0, 0, dw, dh);
}

// Builds a reference image showing the design placed on the actual product photo
// (garment + artwork composited together) — separate from the clean, print-ready,
// transparent-background export. Returns null if there's no real photo to composite
// onto (illustration-only products skip this; there's nothing photographic to show).
async function composePlacementImage(
  photoUrl: string,
  recolor: { tint: boolean; color: string } | null,
  stage: Konva.Stage,
  box: { left: number; top: number; w: number; h: number },
): Promise<string> {
  const canvas = document.createElement('canvas');
  canvas.width = DISPLAY * CAPTURE_SCALE;
  canvas.height = DISPLAY * CAPTURE_SCALE;
  const ctx = canvas.getContext('2d');
  if (!ctx) return '';

  const photoImg = await loadImage(photoUrl);

  if (recolor?.tint) {
    ctx.filter = 'grayscale(1) brightness(2.1) contrast(1.15)';
    drawCover(ctx, photoImg, canvas.width, canvas.height);
    ctx.filter = 'none';
    ctx.globalCompositeOperation = 'multiply';
    ctx.fillStyle = recolor.color;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.globalCompositeOperation = 'source-over';
  } else {
    drawCover(ctx, photoImg, canvas.width, canvas.height);
  }

  const designCanvas = stage.toCanvas({ pixelRatio: CAPTURE_SCALE });
  ctx.drawImage(
    designCanvas,
    box.left * CAPTURE_SCALE,
    box.top * CAPTURE_SCALE,
    box.w * CAPTURE_SCALE,
    box.h * CAPTURE_SCALE,
  );

  return canvas.toDataURL('image/png');
}

const FONTS = [
  'Inter',
  'Fraunces',
  'Caveat',
  'Playfair Display',
  'Oswald',
  'Bebas Neue',
  'Pacifico',
  'Permanent Marker',
  'JetBrains Mono',
  'Georgia',
  'Arial',
];

function UploadedImage({
  el,
  onSelect,
  onChange,
  shapeRef,
}: {
  el: Extract<DesignElement, { type: 'image' }>;
  onSelect: () => void;
  onChange: (attrs: Partial<DesignElement>) => void;
  shapeRef: (node: Konva.Image | null) => void;
}) {
  const [img, setImg] = useState<HTMLImageElement | null>(null);

  useEffect(() => {
    const image = new window.Image();
    image.src = el.src;
    image.onload = () => setImg(image);
  }, [el.src]);

  if (!img) return null;

  return (
    <KonvaImage
      ref={shapeRef}
      image={img}
      x={el.x}
      y={el.y}
      width={el.width}
      height={el.height}
      rotation={el.rotation}
      draggable
      listening={true}
      onClick={onSelect}
      onTap={onSelect}
      onDragEnd={(e) => onChange({ x: e.target.x(), y: e.target.y() })}
      onTransformEnd={(e) => {
        const node = e.target;
        onChange({
          x: node.x(),
          y: node.y(),
          rotation: node.rotation(),
          width: Math.max(10, node.width() * node.scaleX()),
          height: Math.max(10, node.height() * node.scaleY()),
        });
        node.scaleX(1);
        node.scaleY(1);
      }}
    />
  );
}

// One print surface (photo + the draggable design layer on top). Front and back
// each get their own instance, both always mounted (just shown/hidden with CSS)
// so their Konva stages stay exportable even when you're looking at the other side.
function PrintSurface({
  photo,
  printArea,
  elements,
  selectedId,
  setSelectedId,
  updateElement,
  nodeRefs,
  stageRef,
  transformerRef,
  visible,
  caption,
  onNodeReady,
  scale,
}: {
  photo: React.ReactNode;
  printArea: PrintArea;
  elements: DesignElement[];
  selectedId: string | null;
  setSelectedId: (id: string | null) => void;
  updateElement: (id: string, attrs: Partial<DesignElement>) => void;
  nodeRefs: React.MutableRefObject<Map<string, Konva.Node>>;
  stageRef: React.RefObject<Konva.Stage>;
  transformerRef: React.RefObject<Konva.Transformer>;
  visible: boolean;
  caption: string;
  onNodeReady: () => void;
  scale: number;
}) {
  const stageW = printArea.wPct * DISPLAY;
  const stageH = printArea.hPct * DISPLAY;
  const stageLeft = printArea.xPct * DISPLAY;
  const stageTop = printArea.yPct * DISPLAY;

  return (
    <div className={visible ? '' : 'hidden'}>
      {/* Konva's Stage stays a fixed 560x560 internally so element coordinates,
          print-area math, and export resolution never change — only the visual
          box shrinks via CSS transform to fit narrow viewports. */}
      <div style={{ width: DISPLAY * scale, height: DISPLAY * scale }}>
      <div
        className="relative border border-line bg-surface"
        style={{ width: DISPLAY, height: DISPLAY, transform: `scale(${scale})`, transformOrigin: 'top left' }}
      >
        <div className="pointer-events-none absolute inset-0" style={{ width: DISPLAY, height: DISPLAY }}>
          {photo}
        </div>
        <div className="absolute" style={{ left: stageLeft, top: stageTop, width: stageW, height: stageH }}>
          <Stage
            ref={stageRef}
            width={stageW}
            height={stageH}
            onMouseDown={(e) => {
              if (e.target === e.target.getStage()) setSelectedId(null);
            }}
          >
            <Layer>
              <Rect
                name="printGuide"
                x={0}
                y={0}
                width={stageW}
                height={stageH}
                stroke="#E5227E"
                dash={[10, 6]}
                strokeWidth={3}
                shadowColor="#E5227E"
                shadowBlur={12}
                shadowOpacity={0.5}
                listening={false}
              />
              <Rect
                name="printGuide"
                x={0}
                y={0}
                width={stageW}
                height={stageH}
                fill="#E5227E"
                opacity={0.07}
                listening={false}
              />
              {elements.map((el) =>
                el.type === 'text' ? (
                  <Text
                    key={el.id}
                    ref={(node) => {
                      if (node) nodeRefs.current.set(el.id, node);
                    }}
                    x={el.x}
                    y={el.y}
                    text={el.text}
                    fontFamily={el.fontFamily}
                    fontSize={el.fontSize}
                    fill={el.fill}
                    rotation={el.rotation}
                    draggable
                    listening={true}
                    onClick={() => setSelectedId(el.id)}
                    onTap={() => setSelectedId(el.id)}
                    onDragEnd={(e) => updateElement(el.id, { x: e.target.x(), y: e.target.y() })}
                    onTransformEnd={(e) => {
                      const node = e.target;
                      updateElement(el.id, {
                        x: node.x(),
                        y: node.y(),
                        rotation: node.rotation(),
                        fontSize: Math.max(8, el.fontSize * node.scaleX()),
                      });
                      node.scaleX(1);
                      node.scaleY(1);
                    }}
                  />
                ) : (
                  <UploadedImage
                    key={el.id}
                    el={el}
                    onSelect={() => setSelectedId(el.id)}
                    onChange={(attrs) => updateElement(el.id, attrs)}
                    shapeRef={(node) => {
                      if (node && nodeRefs.current.get(el.id) !== node) {
                        nodeRefs.current.set(el.id, node);
                        onNodeReady();
                      }
                    }}
                  />
                ),
              )}
              <Transformer
                ref={transformerRef}
                rotateEnabled
                flipEnabled={false}
                rotateAnchorOffset={30}
                anchorCornerRadius={4}
                anchorSize={8}
                borderStroke="#17181C"
                borderStrokeWidth={2}
                anchorStroke="#17181C"
                anchorFill="#FFF"
                boundBoxFunc={(oldBox, newBox) => (newBox.width < 8 || newBox.height < 8 ? oldBox : newBox)}
              />
            </Layer>
          </Stage>
        </div>
      </div>
      </div>
      <p className="tag-label mt-3">{caption}</p>
    </div>
  );
}

export default function DesignCanvas({ product }: { product: ProductDTO }) {
  const [color, setColor] = useState(product.colors[0]);
  const [size, setSize] = useState<string | null>(product.sizes[0] ?? null);
  const [view, setView] = useState<'front' | 'back'>('front');
  const [elementsFront, setElementsFront] = useState<DesignElement[]>([]);
  const [elementsBack, setElementsBack] = useState<DesignElement[]>([]);
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [quantity, setQuantity] = useState(1);
  const [justAdded, setJustAdded] = useState(false);
  const [showLoginPrompt, setShowLoginPrompt] = useState(false);
  const [nodeReadyTick, setNodeReadyTick] = useState(0);

  const stageFrontRef = useRef<Konva.Stage | null>(null);
  const stageBackRef = useRef<Konva.Stage | null>(null);
  const transformerFrontRef = useRef<Konva.Transformer | null>(null);
  const transformerBackRef = useRef<Konva.Transformer | null>(null);
  const nodeRefs = useRef<Map<string, Konva.Node>>(new Map());
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const canvasContainerRef = useRef<HTMLDivElement | null>(null);
  const [canvasScale, setCanvasScale] = useState(1);
  const addItem = useCart((s) => s.addItem);

  // Konva's Stage keeps a fixed 560x560 internal pixel size (element x/y,
  // print-area math, and export resolution all key off DISPLAY) — on narrow
  // viewports we shrink it purely visually via CSS transform instead of
  // touching that coordinate system. Konva reads the container's actual
  // rendered (post-transform) bounding box for pointer math, so clicks/drags
  // still line up correctly at any scale.
  useLayoutEffect(() => {
    const el = canvasContainerRef.current;
    if (!el) return;
    const update = () => setCanvasScale(Math.min(1, el.clientWidth / DISPLAY));
    update();
    const observer = new ResizeObserver(update);
    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  const hasBack = Boolean(product.imageUrlBack);
  const canPersonalize = product.showOnPersonalised;
  const frontPrintArea = product.printArea;
  const backPrintArea = product.printAreaBack ?? product.printArea;
  const activePrintArea = view === 'front' ? frontPrintArea : backPrintArea;
  const activeStageW = activePrintArea.wPct * DISPLAY;
  const activeStageH = activePrintArea.hPct * DISPLAY;
  const setActiveElements = view === 'front' ? setElementsFront : setElementsBack;

  // Keep whichever transformer actually owns the selected element in sync, and
  // clear the other one — an id only ever lives in one of the two arrays.
  // nodeReadyTick re-runs this once an uploaded image's Konva node actually
  // mounts (it loads asynchronously, unlike text, so it isn't there yet on the
  // same render where it gets selected).
  useEffect(() => {
    const frontNode = selectedId && elementsFront.some((e) => e.id === selectedId) ? nodeRefs.current.get(selectedId) : null;
    const backNode = selectedId && elementsBack.some((e) => e.id === selectedId) ? nodeRefs.current.get(selectedId) : null;
    if (transformerFrontRef.current) {
      transformerFrontRef.current.nodes(frontNode ? [frontNode] : []);
      transformerFrontRef.current.getLayer()?.draw();
    }
    if (transformerBackRef.current) {
      transformerBackRef.current.nodes(backNode ? [backNode] : []);
      transformerBackRef.current.getLayer()?.draw();
    }
  }, [selectedId, elementsFront.length, elementsBack.length, nodeReadyTick]);

  // Canvas text can render with a fallback font if drawn before a web font finishes
  // loading — redraw once fonts are ready so custom font choices show up correctly.
  useEffect(() => {
    document.fonts?.ready.then(() => {
      stageFrontRef.current?.getLayers().forEach((layer) => layer.batchDraw());
      stageBackRef.current?.getLayers().forEach((layer) => layer.batchDraw());
    });
  }, []);

  const updateElement = useCallback((id: string, attrs: Partial<DesignElement>) => {
    setElementsFront((prev) =>
      prev.some((el) => el.id === id) ? prev.map((el) => (el.id === id ? ({ ...el, ...attrs } as DesignElement) : el)) : prev,
    );
    setElementsBack((prev) =>
      prev.some((el) => el.id === id) ? prev.map((el) => (el.id === id ? ({ ...el, ...attrs } as DesignElement) : el)) : prev,
    );
  }, []);

  const addText = () => {
    const id = uuid();
    setActiveElements((prev) => [
      ...prev,
      {
        id,
        type: 'text',
        x: activeStageW / 2 - 40,
        y: activeStageH / 2 - 10,
        rotation: 0,
        text: 'Your text',
        fontFamily: 'Fraunces',
        fontSize: 28,
        fill: '#17181C',
      },
    ]);
    setSelectedId(id);
  };

  const handleUploadClick = () => fileInputRef.current?.click();

  const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      const src = reader.result as string;
      const img = new window.Image();
      img.onload = () => {
        const maxDim = Math.min(activeStageW, activeStageH) * 0.7;
        const scale = Math.min(maxDim / img.width, maxDim / img.height, 1);
        const id = uuid();
        setActiveElements((prev) => [
          ...prev,
          {
            id,
            type: 'image',
            x: activeStageW / 2 - (img.width * scale) / 2,
            y: activeStageH / 2 - (img.height * scale) / 2,
            rotation: 0,
            width: img.width * scale,
            height: img.height * scale,
            src,
          },
        ]);
        setSelectedId(id);
      };
      img.src = src;
    };
    reader.readAsDataURL(file);
    e.target.value = '';
  };

  const deleteSelected = () => {
    if (!selectedId) return;
    setElementsFront((prev) => prev.filter((el) => el.id !== selectedId));
    setElementsBack((prev) => prev.filter((el) => el.id !== selectedId));
    setSelectedId(null);
  };

  const selected = elementsFront.find((el) => el.id === selectedId) ?? elementsBack.find((el) => el.id === selectedId);

  // Calculate the effective price for made-to-order items (use personalisedPrice if available)
  const effectivePriceForPersonalised = canPersonalize
    ? getEffectivePrice(
        {
          id: product.id,
          basePrice: product.basePrice,
          personalisedPrice: product.personalisedPrice,
          salePrice: product.onSale ? product.effectivePrice : null,
          saleStartsAt: null,
          saleEndsAt: null,
        },
        [],
        new Date(),
        true,
      ).price
    : product.effectivePrice;

  const handleAddToBag = async () => {
    const { isAuthenticated } = await checkCustomerAuth();
    if (!isAuthenticated) {
      setShowLoginPrompt(true);
    }

    if (!canPersonalize) {
      const flatPhoto = product.colorPhotos[color] ?? product.imageUrl ?? '';
      addItem({
        cartItemId: uuid(),
        productId: product.id,
        slug: product.slug,
        name: product.name,
        mockup: product.mockup,
        color,
        size,
        quantity,
        unitPrice: product.effectivePrice,
        designJson: { front: [], back: [] },
        designPreviewUrl: flatPhoto,
        designPreviewUrlBack: null,
        placementPreviewUrl: flatPhoto || null,
        placementPreviewUrlBack: product.imageUrlBack ?? null,
      });
      setJustAdded(true);
      setTimeout(() => setJustAdded(false), 2200);
      return;
    }

    setSelectedId(null); // clear transformer handles before export
    requestAnimationFrame(async () => {
      const frontStage = stageFrontRef.current;
      if (!frontStage) return;

      // Hide the dashed guide box + tint before exporting, so the saved PNG is
      // just the design itself — transparent background, no shirt, no guide
      // marks — ready to hand off for printing.
      const frontGuides = frontStage.find('.printGuide');
      frontGuides.forEach((g) => g.hide());
      const frontDataUrl = frontStage.toDataURL({ pixelRatio: 2 });

      const frontBox = {
        left: frontPrintArea.xPct * DISPLAY,
        top: frontPrintArea.yPct * DISPLAY,
        w: frontPrintArea.wPct * DISPLAY,
        h: frontPrintArea.hPct * DISPLAY,
      };
      const frontPhotoInfo = getPhotoInfo('front');
      const frontPlacementUrl = frontPhotoInfo
        ? await composePlacementImage(
            frontPhotoInfo.url,
            frontPhotoInfo.tint ? { tint: true, color } : null,
            frontStage,
            frontBox,
          ).catch(() => null)
        : null;
      frontGuides.forEach((g) => g.show());

      let backDataUrl: string | null = null;
      let backPlacementUrl: string | null = null;
      if (hasBack && stageBackRef.current) {
        const backStage = stageBackRef.current;
        const backGuides = backStage.find('.printGuide');
        backGuides.forEach((g) => g.hide());
        backDataUrl = backStage.toDataURL({ pixelRatio: 2 });

        const backBox = {
          left: backPrintArea.xPct * DISPLAY,
          top: backPrintArea.yPct * DISPLAY,
          w: backPrintArea.wPct * DISPLAY,
          h: backPrintArea.hPct * DISPLAY,
        };
        const backPhotoInfo = getPhotoInfo('back');
        backPlacementUrl = backPhotoInfo
          ? await composePlacementImage(
              backPhotoInfo.url,
              backPhotoInfo.tint ? { tint: true, color } : null,
              backStage,
              backBox,
            ).catch(() => null)
          : null;
        backGuides.forEach((g) => g.show());
      }

      addItem({
        cartItemId: uuid(),
        productId: product.id,
        slug: product.slug,
        name: product.name,
        mockup: product.mockup,
        color,
        size,
        quantity,
        unitPrice: effectivePriceForPersonalised,
        designJson: { front: elementsFront, back: elementsBack },
        designPreviewUrl: frontDataUrl,
        designPreviewUrlBack: backDataUrl,
        placementPreviewUrl: frontPlacementUrl ?? frontDataUrl,
        placementPreviewUrlBack: backPlacementUrl ?? backDataUrl,
      });
      setJustAdded(true);
      setTimeout(() => setJustAdded(false), 2200);
    });
  };

  function renderPhoto(v: 'front' | 'back') {
    const colorPhoto = v === 'front' ? product.colorPhotos[color] : product.colorPhotosBack[color];
    if (colorPhoto) {
      return <img src={colorPhoto} alt={`${product.name} in ${color}`} className="h-full w-full object-cover" />;
    }

    const activePhoto = v === 'front' ? product.imageUrl : product.imageUrlBack;
    if (!activePhoto) {
      return <ProductMockup mockup={product.mockup} color={color} />;
    }
    if (product.photoRecolorable) {
      return (
        <div className="relative h-full w-full">
          <img
            src={activePhoto}
            alt={product.name}
            className="h-full w-full object-cover"
            style={{ filter: 'grayscale(1) brightness(2.1) contrast(1.15)' }}
          />
          <div className="absolute inset-0" style={{ backgroundColor: color, mixBlendMode: 'multiply' }} />
        </div>
      );
    }
    return <img src={activePhoto} alt={product.name} className="h-full w-full object-cover" />;
  }

  // Same resolution logic as renderPhoto, but returns raw data instead of JSX —
  // used when compositing the placement-reference image for the order record.
  function getPhotoInfo(v: 'front' | 'back'): { url: string; tint: boolean } | null {
    const colorPhoto = v === 'front' ? product.colorPhotos[color] : product.colorPhotosBack[color];
    if (colorPhoto) return { url: colorPhoto, tint: false };

    const activePhoto = v === 'front' ? product.imageUrl : product.imageUrlBack;
    if (!activePhoto) return null; // illustration only — nothing photographic to composite onto

    return { url: activePhoto, tint: product.photoRecolorable };
  }

  return (
    <div className="grid grid-cols-1 gap-10 lg:grid-cols-[560px_1fr]">
      {/* Canvas */}
      <div ref={canvasContainerRef} className="w-full min-w-0 max-w-[560px]">
        {hasBack && (
          <div className="mb-3 inline-flex border border-line">
            <button
              onClick={() => setView('front')}
              className={`px-4 py-1.5 text-sm ${view === 'front' ? 'bg-ink text-paper' : 'hover:text-clay'}`}
            >
              Front
            </button>
            <button
              onClick={() => setView('back')}
              className={`px-4 py-1.5 text-sm ${view === 'back' ? 'bg-ink text-paper' : 'hover:text-clay'}`}
            >
              Back
            </button>
          </div>
        )}

        {canPersonalize ? (
          <>
            <PrintSurface
              photo={renderPhoto('front')}
              printArea={frontPrintArea}
              elements={elementsFront}
              selectedId={selectedId}
              setSelectedId={setSelectedId}
              updateElement={updateElement}
              nodeRefs={nodeRefs}
              stageRef={stageFrontRef}
              transformerRef={transformerFrontRef}
              visible={view === 'front'}
              caption="Dashed line marks the print area"
              onNodeReady={() => setNodeReadyTick((t) => t + 1)}
              scale={canvasScale}
            />

            {hasBack && (
              <PrintSurface
                photo={renderPhoto('back')}
                printArea={backPrintArea}
                elements={elementsBack}
                selectedId={selectedId}
                setSelectedId={setSelectedId}
                updateElement={updateElement}
                nodeRefs={nodeRefs}
                stageRef={stageBackRef}
                transformerRef={transformerBackRef}
                visible={view === 'back'}
                caption="Dashed line marks the print area (back)"
                onNodeReady={() => setNodeReadyTick((t) => t + 1)}
                scale={canvasScale}
              />
            )}
          </>
        ) : (
          <div className="relative aspect-square w-full max-w-[560px] border border-line bg-surface">
            {renderPhoto(view)}
          </div>
        )}
      </div>

      {/* Controls */}
      <div className="flex flex-col gap-8">
        <div>
          <h1 className="font-display text-4xl">{product.name}</h1>
          <p className="mt-2 text-muted">{product.description}</p>
          <p className="mt-4 flex items-baseline gap-2 font-mono text-lg">
            {(() => {
              const { onSale, price, originalPrice } = getEffectivePrice(
                {
                  id: product.id,
                  basePrice: product.basePrice,
                  personalisedPrice: product.personalisedPrice,
                  salePrice: product.onSale ? product.effectivePrice : null,
                  saleStartsAt: null,
                  saleEndsAt: null,
                },
                [],
                new Date(),
                true,
              );
              return (
                <>
                  {onSale && originalPrice != null && (
                    <Price cents={originalPrice} className="text-base text-muted line-through" />
                  )}
                  <Price cents={price} className={onSale ? 'text-splash-pink' : undefined} />
                </>
              );
            })()}
          </p>
        </div>

        <div>
          <p className="tag-label mb-3">Color</p>
          <div className="flex flex-wrap gap-3">
            {product.colors.map((c) => (
              <button
                key={c}
                onClick={() => setColor(c)}
                aria-label={`Choose color ${c}`}
                className={`h-9 w-9 rounded-full border transition-transform ${
                  color === c ? 'scale-110 border-ink ring-2 ring-ink ring-offset-2 ring-offset-paper' : 'border-line'
                }`}
                style={{ backgroundColor: c }}
              />
            ))}
          </div>
          {product.imageUrl && !product.photoRecolorable && Object.keys(product.colorPhotos).length === 0 && (
            <p className="mt-2 text-xs text-muted">
              This product uses a real photo, so the picture won&apos;t change color — your pick is
              still saved with the order.
            </p>
          )}
        </div>

        {product.sizes.length > 0 && (
          <div>
            <p className="tag-label mb-3">Size</p>
            <div className="flex flex-wrap gap-2">
              {product.sizes.map((s) => (
                <button
                  key={s}
                  onClick={() => setSize(s)}
                  className={`min-w-[44px] border px-3 py-2 text-sm transition-colors ${
                    size === s
                      ? 'border-clay bg-clay text-paper'
                      : 'border-line text-ink hover:border-clay hover:text-clay'
                  }`}
                >
                  {s}
                </button>
              ))}
            </div>
          </div>
        )}

        {canPersonalize && (
        <div>
          <p className="tag-label mb-3">
            Your design {hasBack && <span className="normal-case text-muted">— editing the {view}</span>}
          </p>
          <div className="flex flex-wrap gap-3">
            <button onClick={addText} className="border border-ink px-4 py-2 text-sm hover:border-clay hover:bg-clay hover:text-paper">
              + Add text
            </button>
            <button
              onClick={handleUploadClick}
              className="border border-ink px-4 py-2 text-sm hover:border-clay hover:bg-clay hover:text-paper"
            >
              + Upload image
            </button>
            <input ref={fileInputRef} type="file" accept="image/*" className="hidden" onChange={handleFile} />
            {selected && (
              <button
                onClick={deleteSelected}
                className="border border-line px-4 py-2 text-sm text-muted hover:border-ink hover:text-ink"
              >
                Delete selected
              </button>
            )}
          </div>

          {selected?.type === 'text' && (
            <div className="mt-4 flex flex-wrap items-center gap-3 border border-line bg-surface p-4">
              <input
                value={selected.text}
                onChange={(e) => updateElement(selected.id, { text: e.target.value })}
                className="border border-line bg-paper px-2 py-1 text-sm text-ink placeholder:text-muted"
                placeholder="Text"
              />
              <select
                value={selected.fontFamily}
                onChange={(e) => updateElement(selected.id, { fontFamily: e.target.value })}
                className="border border-line bg-paper px-2 py-1 text-sm text-ink"
                style={{ fontFamily: selected.fontFamily }}
              >
                {FONTS.map((f) => (
                  <option key={f} value={f} style={{ fontFamily: f }}>
                    {f}
                  </option>
                ))}
              </select>
              <input
                type="color"
                value={selected.fill}
                onChange={(e) => updateElement(selected.id, { fill: e.target.value })}
                className="h-8 w-8 border border-line bg-paper p-0"
              />
              <label className="flex items-center gap-2 text-sm text-muted">
                Size
                <input
                  type="range"
                  min={10}
                  max={80}
                  value={selected.fontSize}
                  onChange={(e) => updateElement(selected.id, { fontSize: Number(e.target.value) })}
                />
              </label>
              <label className="flex items-center gap-2 text-sm text-muted">
                Angle
                <input
                  type="range"
                  min={0}
                  max={360}
                  value={selected.rotation || 0}
                  onChange={(e) => updateElement(selected.id, { rotation: Number(e.target.value) })}
                />
                <span className="w-10 text-right font-mono text-xs">{selected.rotation || 0}°</span>
              </label>
            </div>
          )}

          {selected?.type === 'image' && (
            <div className="mt-4 flex flex-wrap items-center gap-3 border border-line bg-surface p-4">
              <label className="flex items-center gap-2 text-sm text-muted">
                Angle
                <input
                  type="range"
                  min={0}
                  max={360}
                  value={selected.rotation || 0}
                  onChange={(e) => updateElement(selected.id, { rotation: Number(e.target.value) })}
                />
                <span className="w-10 text-right font-mono text-xs">{selected.rotation || 0}°</span>
              </label>
            </div>
          )}
        </div>
        )}

        <div className="flex items-center gap-4">
          <div className="flex items-center border border-ink">
            <button
              onClick={() => setQuantity((q) => Math.max(1, q - 1))}
              className="px-3 py-2 text-sm"
              aria-label="Decrease quantity"
            >
              −
            </button>
            <span className="w-10 text-center font-mono text-sm">{quantity}</span>
            <button
              onClick={() => setQuantity((q) => q + 1)}
              className="px-3 py-2 text-sm"
              aria-label="Increase quantity"
            >
              +
            </button>
          </div>
          <button
            onClick={handleAddToBag}
            className="flex-1 bg-clay px-6 py-3 text-sm font-medium text-paper transition-colors hover:bg-clay-dark"
          >
            Add to bag — <Price cents={product.effectivePrice * quantity} />
          </button>
        </div>
        {justAdded && <p className="text-sm text-pine">Added to your bag.</p>}
      </div>

      <LoginPromptModal isOpen={showLoginPrompt} onClose={() => setShowLoginPrompt(false)} />
    </div>
  );
}
