import { useRef, useState } from 'react';
import {
    IconTrash, IconPhotoUp, IconCamera, IconFileDownload, IconLink, IconLinkOff,
    IconFrame, IconCopyPlus, IconPlus, IconMinus, IconCrop, IconCheck,
    IconArrowsMaximize, IconArrowsMinimize, IconRestore, IconWand, IconLoader2,
    IconArrowsDiagonalMinimize2,
} from '@tabler/icons-react';
import {
    PanelSection, Row, RowLabel, NumInput, FigSelect, FigCheck, IconBtn,
    figInputCls, figTextareaCls, T,
} from '../../ui/figma';
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
import { ColorRow } from '../../ui/ColorControl';
import {
    SIZE_PRESETS, constrainCanvasSize, CANVAS_MIN, CANVAS_MAX, isLockedFor,
} from '../../schema/documentSchema';
import {
    SHAPE_DEFS, getShapeDef, resolveShapeParams, shapeParamFields, shapeElementDefaults,
    isFreeformElement,
} from '../../schema/shapeLibrary';
import { shapeGlyphIcon } from '../../ui/shapeGlyph';
import ThumbnailCropDialog from '../../ui/ThumbnailCropDialog';
import { getCrop } from '../../utils/imageCrop';
import { canRemoveBackground } from '../../utils/removeImageBackground';
import { dataUrlBytes, measureSrcBytes } from '../../utils/imageOptimize';
import {
    readImageTransfer, imageSourceToPayloads, filesToImagePayloads, formatFileSize,
} from '../../utils/imageFiles';
import { QR_LOGO_DEFAULTS, hasQrLogo } from '../../utils/qrCode';
import { matchPresetId } from '../propertyControls';
import { FillControls } from './controls';
import { usePrintEditorStore } from '../../state/usePrintEditorStore';

const checker = {
    backgroundImage:
        'linear-gradient(45deg, #e4e4e7 25%, transparent 25%, transparent 75%, #e4e4e7 75%), linear-gradient(45deg, #e4e4e7 25%, transparent 25%, transparent 75%, #e4e4e7 75%)',
    backgroundSize: '12px 12px',
    backgroundPosition: '0 0, 6px 6px',
};

/* ------------------------------------------------------------------ *
 *  Shape — swap the outline, tune its parameters
 * ------------------------------------------------------------------ */

export function ShapeSection({ el, setProp, mode }) {
    const locked = mode === 'user' && el.editableByUser === false;
    const def = getShapeDef(el.shape);
    const params = resolveShapeParams(el);
    const fields = shapeParamFields(el.shape);

    // Swapping keeps the box the user already sized and re-seeds the new shape's
    // own parameters. The layer name only follows along while it is still the
    // previous shape's default — a renamed layer keeps its name.
    const switchShape = (key) => {
        if (locked || key === el.shape) return;
        const next = shapeElementDefaults(key);
        setProp({
            shape: next.shape,
            ...next.params,
            ...(el.name === def.label ? { name: next.name } : null),
        });
    };

    // A hand-drawn path has no catalogue entry to switch between, and swapping it
    // for a generated shape would throw the drawing away with no way back.
    if (isFreeformElement(el)) {
        return (
            <PanelSection title="Shape">
                <p className={`text-[10px] leading-snug ${T.textFaint}`}>
                    Drawn with the {el.name === 'Pencil path' ? 'pencil' : 'pen'}. Its outline is part
                    of this layer — resize the box to scale it, and use Fill and Border below to
                    style it like any other shape.
                </p>
            </PanelSection>
        );
    }

    return (
        <PanelSection title="Shape">
            <div className="grid grid-cols-6 gap-1 mb-2">
                {SHAPE_DEFS.filter((s) => !s.hidden).map((s) => {
                    const Icon = shapeGlyphIcon(s.key);
                    const active = s.key === def.key;
                    return (
                        <button
                            key={s.key}
                            type="button"
                            title={s.label}
                            aria-label={s.label}
                            aria-pressed={active}
                            disabled={locked}
                            onClick={() => switchShape(s.key)}
                            className={`h-7 flex items-center justify-center rounded-[5px] border transition-colors
                                disabled:opacity-40 disabled:pointer-events-none
                                ${active
                                    ? 'border-[#0d99ff] text-[#0d99ff] bg-[#0d99ff]/10'
                                    : `${T.border} ${T.text} ${T.hoverBg}`}`}
                        >
                            <Icon size={14} stroke={1.5} />
                        </button>
                    );
                })}
            </div>
            {fields.length > 0 && (
                <Row cols={2}>
                    {fields.map((f) => (
                        <NumInput
                            key={f.prop}
                            label={f.label}
                            title={f.label}
                            value={params[f.prop] ?? f.default}
                            min={f.min}
                            max={f.max}
                            step={f.step}
                            suffix={f.suffix || ''}
                            disabled={locked}
                            onChange={(v) => setProp({ [f.prop]: v })}
                        />
                    ))}
                    {fields.length % 2 === 1 && <span />}
                </Row>
            )}
        </PanelSection>
    );
}

/* ------------------------------------------------------------------ *
 *  Image — preview, replace/remove, crop, fit + position
 * ------------------------------------------------------------------ */

/**
 * Crop actions (Fill / Fit / Reset / Done) — the same 28px button the rest of the
 * panel uses for "Duplicate frame" / "Use design as thumbnail", with the primary
 * variant matching "Replace image" right above it.
 */
function CropBtn({ icon: Icon, label, title, onClick, disabled = false, primary = false }) {
    return (
        <button
            type="button"
            title={title || label}
            disabled={disabled}
            onClick={onClick}
            className={`flex-1 min-w-0 h-7 flex items-center justify-center gap-1.5 rounded-[5px]
                text-[11px] font-medium transition-colors
                disabled:opacity-40 disabled:pointer-events-none
                ${primary
                    ? 'bg-[#0d99ff] text-white hover:bg-[#0b87e0]'
                    : `border ${T.border} ${T.text} ${T.hoverBg}`}`}
        >
            <Icon size={13} stroke={1.75} className="shrink-0" />
            <span className="truncate">{label}</span>
        </button>
    );
}

/**
 * Cutting the subject out, in the Image header beside the other picture actions.
 *
 * The work runs in the STORE and can take minutes on a cold model, so nothing
 * here owns it: closing this popover, or selecting another layer, leaves the job
 * running and reopening finds it exactly where it was. The layers list keeps
 * showing the layer's own progress meanwhile.
 */
function RemoveBackgroundPopover({ el, disabled }) {
    const job = usePrintEditorStore((s) => s.bgRemoval[el.id]) || {};
    const store = usePrintEditorStore.getState;
    const busy = !!job.busy;
    const supported = canRemoveBackground(el.src);
    // The engine reports one number for the whole run, and a model already in the
    // browser's cache reports complete the moment it is asked for — so on every
    // run after the first it reads 100% before the work has started. A finished
    // number in front of an unfinished job says less than no number at all, so it
    // is only shown while it still means something.
    const pct = busy && job.pct > 0 && job.pct < 100 ? job.pct : null;

    return (
        <Popover
            onOpenChange={(open) => {
                // A dismissed failure must not be waiting the next time this opens.
                if (!open && job.error) store().clearBackgroundRemoval(el.id);
            }}
        >
            <PopoverTrigger asChild>
                <span className="inline-flex">
                    <IconBtn
                        icon={busy ? IconLoader2 : IconWand}
                        // The only place left to say why the button inside is dead
                        // on a vector layer, now that the panel carries no prose.
                        label={supported
                            ? 'Remove background — cut the subject out'
                            : 'Only photos can be cut out — this layer is a vector'}
                        active={busy}
                        disabled={disabled || !el.src}
                    />
                </span>
            </PopoverTrigger>
            <PopoverContent align="end" className="w-64 p-2.5">
                <div className={`text-[11px] font-medium mb-1.5 ${T.text}`}>Remove background</div>
                {job.error && (
                    <p className="mb-1.5 text-[10px] leading-snug text-[#f24822]">{job.error}</p>
                )}
                <button
                    type="button"
                    disabled={disabled || !el.src || !supported || busy}
                    onClick={() => store().removeElementBackground(el.id)}
                    className={`w-full h-7 flex items-center justify-center gap-1.5 rounded-[5px]
                        text-[11px] font-medium bg-[#0d99ff] text-white hover:bg-[#0b87e0]
                        disabled:opacity-40 disabled:pointer-events-none transition-colors`}
                >
                    {busy ? (
                        <>
                            <IconLoader2 size={13} className="animate-spin" />
                            {pct != null ? `Removing… ${pct}%` : 'Removing…'}
                        </>
                    ) : (
                        <><IconWand size={13} stroke={1.75} /> Remove background</>
                    )}
                </button>
            </PopoverContent>
        </Popover>
    );
}

/**
 * "How heavy is this picture, and can it be lighter?" — in a popover, because it
 * is a question with an answer rather than a control with a value.
 *
 * Shows what the layer costs the template now, offers to shrink it, and keeps
 * the way back: the original is held for the session, so a result that looks
 * worse than the photo it came from is one click from being undone.
 */
function OptimizeImagePopover({ el, disabled }) {
    const job = usePrintEditorStore((s) => s.imageOptimize[el.id]) || {};
    const original = usePrintEditorStore((s) => s.imageOriginals[el.id]);
    const store = usePrintEditorStore.getState;
    const embedded = dataUrlBytes(el.src);
    // A picture already saved to R2 has no bytes in the document to count, so it
    // is weighed on demand — when the panel is opened, not while it is closed and
    // nobody is asking. `null` until the answer comes back.
    const [fetched, setFetched] = useState(null);
    const [weighing, setWeighing] = useState(false);
    const current = embedded ?? fetched;
    const busy = !!job.busy;
    const saved = job.before && job.after ? 1 - job.after / job.before : 0;

    const weigh = async () => {
        if (embedded != null || !el.src || weighing) return;
        setWeighing(true);
        try {
            setFetched(await measureSrcBytes(el.src));
        } finally {
            setWeighing(false);
        }
    };

    return (
        <Popover
            onOpenChange={(open) => {
                if (open) weigh();
                // A dismissed message must not be waiting the next time this is
                // opened; the sizes below are read fresh anyway.
                if (!open && job.error) store().clearImageOptimize(el.id);
            }}
        >
            <PopoverTrigger asChild>
                <span className="inline-flex">
                    <IconBtn
                        icon={IconArrowsDiagonalMinimize2}
                        label="Optimise image — make the file smaller"
                        active={!!original}
                        disabled={disabled || !el.src}
                    />
                </span>
            </PopoverTrigger>
            <PopoverContent align="end" className="w-64 p-2.5">
                <div className={`text-[11px] font-medium mb-1.5 ${T.text}`}>Optimise image</div>

                <div className={`flex items-center justify-between text-[11px] ${T.textSoft}`}>
                    <span>This picture weighs</span>
                    <span className={`tabular-nums ${T.text}`}>
                        {current != null ? formatFileSize(current)
                            : weighing ? 'measuring…'
                                : '—'}
                    </span>
                </div>
                {job.done && job.before != null && (
                    <div className={`flex items-center justify-between text-[11px] mt-1 ${T.textSoft}`}>
                        <span>Was</span>
                        <span className="tabular-nums line-through">{formatFileSize(job.before)}</span>
                    </div>
                )}
                {job.done && (
                    <p className="mt-1.5 text-[10px] leading-snug text-[#14ae5c]">
                        {saved > 0 ? `${Math.round(saved * 100)}% smaller` : 'Re-encoded'}
                        {job.resized ? ` · resized to ${job.width}×${job.height}` : ''}
                    </p>
                )}
                {job.error && (
                    <p className="mt-1.5 text-[10px] leading-snug text-[#f24822]">{job.error}</p>
                )}

                <button
                    type="button"
                    disabled={disabled || !el.src || busy}
                    onClick={() => store().optimizeElementImage(el.id)}
                    className={`w-full h-7 mt-2 flex items-center justify-center gap-1.5 rounded-[5px]
                        text-[11px] font-medium bg-[#0d99ff] text-white hover:bg-[#0b87e0]
                        disabled:opacity-40 disabled:pointer-events-none transition-colors`}
                >
                    {busy ? (
                        <><IconLoader2 size={13} className="animate-spin" /> Optimising…</>
                    ) : (
                        <><IconArrowsDiagonalMinimize2 size={13} stroke={1.75} /> {job.done ? 'Optimise again' : 'Optimise'}</>
                    )}
                </button>
                {original && (
                    <button
                        type="button"
                        disabled={disabled || busy}
                        onClick={() => store().revertElementImage(el.id)}
                        className={`w-full h-7 mt-1 flex items-center justify-center gap-1.5 rounded-[5px]
                            border ${T.border} ${T.text} ${T.hoverBg} text-[11px] font-medium
                            disabled:opacity-40 disabled:pointer-events-none transition-colors`}
                    >
                        <IconRestore size={13} stroke={1.75} />
                        Cancel — put back {formatFileSize(original.bytes ?? 0)}
                    </button>
                )}
            </PopoverContent>
        </Popover>
    );
}

export function ImageSection({
    el, styles, setProp, setStyle, mode, onImageUpload,
    // Crop lives on the canvas (drag the picture, drag the handles); the panel only
    // opens it, re-fits the picture and throws the crop away again.
    cropping = false, onCropToggle, onCropFit, onCropClear,
}) {
    const locked = mode === 'user' && el.editableByUser === false;
    // Crop drags the element itself around, so a locked layer is off limits too —
    // the store refuses it either way, and a dead-looking button says so up front.
    const cropLocked = locked || isLockedFor(el, mode);
    const cropped = !!getCrop(el);

    return (
        <PanelSection
            title="Image"
            actions={(
                <>
                    <RemoveBackgroundPopover el={el} disabled={locked} />
                    <OptimizeImagePopover el={el} disabled={locked} />
                    {onCropToggle ? (
                        <IconBtn
                            icon={IconCrop}
                            label={cropping
                                ? 'Finish cropping (Esc)'
                                : 'Crop image — or double-click it on the canvas'}
                            active={cropping}
                            disabled={cropLocked || !el.src}
                            onClick={onCropToggle}
                        />
                    ) : null}
                </>
            )}
        >
            <div className={`rounded-[6px] border ${T.border} overflow-hidden mb-2`}>
                <div className="aspect-[4/3] flex items-center justify-center overflow-hidden bg-white dark:bg-[#1e1e1e]" style={checker}>
                    {el.src ? (
                        <img src={el.src} alt={el.name || 'Image'} className="max-w-full max-h-full object-contain" />
                    ) : (
                        <span className={`text-[10px] ${T.textSoft} px-3 text-center`}>No image selected</span>
                    )}
                </div>
            </div>
            <div className="flex gap-1 mb-2">
                <label
                    className={`flex-1 h-7 flex items-center justify-center gap-1.5 rounded-[5px] text-[11px] font-medium
                        bg-[#0d99ff] text-white transition-opacity
                        ${locked ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer hover:bg-[#0b87e0]'}`}
                >
                    <IconPhotoUp size={13} />
                    {el.src ? 'Replace image' : 'Upload image'}
                    <input
                        type="file"
                        accept="image/*"
                        multiple
                        className="hidden"
                        disabled={locked}
                        onChange={(e) => {
                            onImageUpload(e.target.files);
                            e.target.value = '';
                        }}
                    />
                </label>
                <button
                    type="button"
                    title="Remove image"
                    disabled={locked || !el.src}
                    onClick={() => setProp({ src: '' })}
                    className={`h-7 w-7 shrink-0 flex items-center justify-center rounded-[5px] border ${T.border}
                        text-[#f24822] ${T.hoverBg} disabled:opacity-30 disabled:pointer-events-none`}
                >
                    <IconTrash size={13} />
                </button>
            </div>
            {/* Crop actions — the canvas does the dragging; these are the two re-fits
                that are awkward by hand, plus the way out. Shown only once a crop
                exists (or while one is being made), so an uncropped image keeps the
                plain Fit / Position pair. */}
            {(cropping || cropped) && (
                <>
                    <RowLabel>Crop</RowLabel>
                    {/* Four actions don't fit one 11px row at the panel's width, so
                        cropping lays them out 2×2; the three-button (cropped, not
                        cropping) state still fits on one line. */}
                    <div className={`grid gap-1 mb-2 ${cropping ? 'grid-cols-2' : 'grid-cols-3'}`}>
                        <CropBtn
                            icon={IconArrowsMaximize}
                            label="Fill"
                            title="Fill the frame with the picture"
                            disabled={cropLocked}
                            onClick={() => onCropFit?.('cover')}
                        />
                        <CropBtn
                            icon={IconArrowsMinimize}
                            label="Fit"
                            title="Fit the whole picture inside the frame"
                            disabled={cropLocked}
                            onClick={() => onCropFit?.('contain')}
                        />
                        <CropBtn
                            icon={IconRestore}
                            label="Reset"
                            title="Remove the crop — back to Fit and Position"
                            disabled={cropLocked || !cropped}
                            onClick={() => onCropClear?.()}
                        />
                        {cropping && (
                            <CropBtn
                                icon={IconCheck}
                                label="Done"
                                title="Finish cropping (Esc)"
                                primary
                                onClick={onCropToggle}
                            />
                        )}
                    </div>
                </>
            )}
            <Row>
                <div>
                    <RowLabel>Fit</RowLabel>
                    <FigSelect
                        value={el.fit || 'contain'}
                        disabled={locked || cropped}
                        onChange={(v) => setProp({ fit: v })}
                        options={[
                            { value: 'contain', label: 'Fit' },
                            { value: 'cover', label: 'Fill' },
                            { value: 'fill', label: 'Stretch' },
                            { value: 'none', label: 'None' },
                            { value: 'scale-down', label: 'Scale down' },
                        ]}
                    />
                </div>
                <div>
                    <RowLabel>Position</RowLabel>
                    <FigSelect
                        value={styles.objectPosition || 'center'}
                        disabled={locked || cropped}
                        onChange={(v) => setStyle('objectPosition', v)}
                        options={[
                            { value: 'center', label: 'Center' },
                            { value: 'top', label: 'Top' },
                            { value: 'bottom', label: 'Bottom' },
                            { value: 'left', label: 'Left' },
                            { value: 'right', label: 'Right' },
                            { value: 'top left', label: 'Top left' },
                            { value: 'top right', label: 'Top right' },
                            { value: 'bottom left', label: 'Bottom left' },
                            { value: 'bottom right', label: 'Bottom right' },
                        ]}
                    />
                </div>
            </Row>
            {/* One trailing hint line, as everywhere else — it says what the gesture
                is while cropping, and goes back to the upload note once done. */}
            <p className={`mt-1.5 text-[10px] leading-snug ${T.textFaint}`}>
                {cropping
                    ? 'On canvas: drag the picture to move it, the white handles to crop, the blue corners to resize it.'
                    : cropped
                        ? 'Cropped — Fit and Position are what the crop replaced.'
                        : 'Selecting multiple files adds the extras to the canvas.'}
            </p>
        </PanelSection>
    );
}

/* ------------------------------------------------------------------ *
 *  QR code
 * ------------------------------------------------------------------ */

export function QrSection({ el, styles, setProp, setStyle, mode }) {
    const locked = mode === 'user' && el.editableByUser === false;
    const hasLogo = hasQrLogo(el);

    /**
     * The logo is stored as the element's own `src` (see utils/qrCode), so it is
     * one field to set — no second element floating on top of the code, which is
     * how people did this before and it broke the moment the QR was moved.
     */
    const uploadLogo = async (files) => {
        const list = Array.from(files || []);
        if (!list.length) return;
        try {
            const [payload] = await filesToImagePayloads(list.slice(0, 1), { maxSide: 512 });
            if (payload?.src) setProp({ src: payload.src });
        } catch (err) {
            console.error('[print-editor] QR logo upload failed', err);
        }
    };

    return (
        <PanelSection title="QR code">
            <RowLabel>Value</RowLabel>
            <input
                className={`${figInputCls} mb-2`}
                value={el.content || ''}
                disabled={locked}
                onChange={(e) => setProp({ content: e.target.value })}
            />
            <RowLabel>Foreground</RowLabel>
            <div className="mb-2">
                <ColorRow value={styles.foreground || '#000000'} onChange={(v) => setStyle('foreground', v)} />
            </div>
            <RowLabel>Background</RowLabel>
            <div className="mb-2">
                <ColorRow value={styles.background || '#ffffff'} allowEmpty onChange={(v) => setStyle('background', v)} />
            </div>
            <Row>
                <NumInput
                    label="Margin"
                    title="Quiet zone margin"
                    value={styles.margin ?? 1}
                    min={0}
                    max={8}
                    onChange={(v) => setStyle('margin', v)}
                />
                <div>
                    <RowLabel>Error correction</RowLabel>
                    <FigSelect
                        title="How much of the code may be covered and still scan"
                        value={styles.errorCorrection || ''}
                        onChange={(v) => setStyle('errorCorrection', v)}
                        options={[
                            { value: '', label: hasLogo ? 'Auto (High)' : 'Auto (Medium)' },
                            { value: 'L', label: 'Low — 7%' },
                            { value: 'M', label: 'Medium — 15%' },
                            { value: 'Q', label: 'Quartile — 25%' },
                            { value: 'H', label: 'High — 30%' },
                        ]}
                    />
                </div>
            </Row>

            {/* Logo in the middle. The code is regenerated with it baked in, so it
                stays put through moves, resizes and the export. */}
            <div className={`mt-3 pt-3 border-t ${T.border}`}>
                <RowLabel>Logo</RowLabel>
                <div className="flex items-center gap-2 mb-2">
                    <div
                        className={`h-11 w-11 shrink-0 rounded-[6px] border ${T.border} overflow-hidden
                            flex items-center justify-center bg-white dark:bg-[#1e1e1e]`}
                        style={checker}
                    >
                        {hasLogo ? (
                            <img src={el.src} alt="QR logo" className="max-w-full max-h-full object-contain" />
                        ) : (
                            <IconPhotoUp size={15} className="text-black/25 dark:text-white/25" />
                        )}
                    </div>
                    <label
                        className={`flex-1 h-7 flex items-center justify-center gap-1.5 rounded-[5px] text-[11px] font-medium
                            bg-[#0d99ff] text-white transition-opacity
                            ${locked ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer hover:bg-[#0b87e0]'}`}
                    >
                        <IconPhotoUp size={13} />
                        {hasLogo ? 'Replace logo' : 'Add logo'}
                        <input
                            type="file"
                            accept="image/*"
                            className="hidden"
                            disabled={locked}
                            onChange={(e) => {
                                uploadLogo(e.target.files);
                                e.target.value = '';
                            }}
                        />
                    </label>
                    <button
                        type="button"
                        title="Remove logo"
                        disabled={locked || !hasLogo}
                        onClick={() => setProp({ src: null })}
                        className={`h-7 w-7 shrink-0 flex items-center justify-center rounded-[5px] border ${T.border}
                            text-[#f24822] ${T.hoverBg} disabled:opacity-30 disabled:pointer-events-none`}
                    >
                        <IconTrash size={13} />
                    </button>
                </div>
                {hasLogo && (
                    <>
                        <Row>
                            <NumInput
                                label="Size"
                                title="Logo size — % of the code. Past ~30% even High correction stops scanning."
                                value={styles.logoSize ?? QR_LOGO_DEFAULTS.logoSize}
                                min={8}
                                max={40}
                                suffix="%"
                                onChange={(v) => setStyle('logoSize', v)}
                            />
                            <NumInput
                                label="Padding"
                                title="Clear space around the logo, % of the plate"
                                value={styles.logoPadding ?? QR_LOGO_DEFAULTS.logoPadding}
                                min={0}
                                max={45}
                                suffix="%"
                                onChange={(v) => setStyle('logoPadding', v)}
                            />
                        </Row>
                        <Row>
                            <NumInput
                                label="Radius"
                                title="Plate corner radius, % of the plate (50% = a circle)"
                                value={styles.logoRadius ?? QR_LOGO_DEFAULTS.logoRadius}
                                min={0}
                                max={50}
                                suffix="%"
                                onChange={(v) => setStyle('logoRadius', v)}
                            />
                            <span />
                        </Row>
                        <div className="mt-2">
                            <RowLabel>Backdrop</RowLabel>
                            <ColorRow
                                value={styles.logoBackground ?? QR_LOGO_DEFAULTS.logoBackground}
                                allowEmpty
                                onChange={(v) => setStyle('logoBackground', v)}
                            />
                        </div>
                        <p className={`mt-1.5 text-[10px] leading-snug ${T.textFaint}`}>
                            Error correction is raised to High automatically, so the covered
                            squares are recovered. Scan it once before printing.
                        </p>
                    </>
                )}
            </div>
        </PanelSection>
    );
}

/* ------------------------------------------------------------------ *
 *  Barcode
 * ------------------------------------------------------------------ */

export function BarcodeSection({ el, styles, setProp, setStyle, mode }) {
    const locked = mode === 'user' && el.editableByUser === false;
    return (
        <PanelSection title="Barcode">
            <RowLabel>Value</RowLabel>
            <input
                className={`${figInputCls} mb-2 font-mono`}
                value={el.content || ''}
                disabled={locked}
                onChange={(e) => setProp({ content: e.target.value })}
            />
            <Row>
                <div>
                    <RowLabel>Format</RowLabel>
                    <FigSelect
                        value={el.format || 'CODE128'}
                        onChange={(v) => setProp({ format: v })}
                        options={['CODE128', 'EAN13', 'EAN8', 'UPC', 'CODE39', 'ITF14', 'MSI', 'pharmacode']
                            .map((f) => ({ value: f, label: f }))}
                    />
                </div>
                <div>
                    <RowLabel>Bar width</RowLabel>
                    <NumInput
                        value={styles.barWidth ?? 1.6}
                        min={0.5}
                        max={4}
                        step={0.1}
                        precision={1}
                        onChange={(v) => setStyle('barWidth', v)}
                    />
                </div>
            </Row>
            <RowLabel>Bars</RowLabel>
            <div className="mb-2">
                <ColorRow value={styles.lineColor || '#000000'} onChange={(v) => setStyle('lineColor', v)} />
            </div>
            <RowLabel>Background</RowLabel>
            <div className="mb-2">
                <ColorRow value={styles.background || '#ffffff'} allowEmpty onChange={(v) => setStyle('background', v)} />
            </div>
            <Row>
                <NumInput
                    label="Margin"
                    value={styles.margin ?? 4}
                    min={0}
                    max={40}
                    onChange={(v) => setStyle('margin', v)}
                />
                <span />
            </Row>
            <FigCheck
                label="Show value text"
                checked={styles.displayValue !== false}
                onChange={(v) => setStyle('displayValue', v)}
            />
        </PanelSection>
    );
}

/* ------------------------------------------------------------------ *
 *  Table — data + styling
 * ------------------------------------------------------------------ */

export function TableSection({ el, styles, setProp, setStyle, mode }) {
    return (
        <>
            <PanelSection title="Table data">
                <RowLabel>Cells (CSV rows)</RowLabel>
                <textarea
                    className={`${figTextareaCls} font-mono`}
                    rows={6}
                    value={(el.cells || []).map((r) => (r || []).join(',')).join('\n')}
                    disabled={mode === 'user'}
                    onChange={(e) => {
                        const cells = e.target.value.split('\n').map((line) => line.split(','));
                        setProp({
                            cells,
                            rows: cells.length,
                            cols: Math.max(...cells.map((r) => r.length), 1),
                        });
                    }}
                />
            </PanelSection>
            <PanelSection title="Table style">
                <RowLabel>Header background</RowLabel>
                <div className="mb-2">
                    <ColorRow value={styles.headerBg || '#f4f4f5'} onChange={(v) => setStyle('headerBg', v)} />
                </div>
                <RowLabel>Cell background</RowLabel>
                <div className="mb-2">
                    <ColorRow value={styles.cellBg || 'transparent'} allowEmpty onChange={(v) => setStyle('cellBg', v)} />
                </div>
                <RowLabel>Borders</RowLabel>
                <div className="mb-2">
                    <ColorRow value={styles.borderColor || '#d4d4d8'} onChange={(v) => setStyle('borderColor', v)} />
                </div>
                <Row>
                    <NumInput
                        label="W"
                        title="Border width"
                        value={styles.borderWidth ?? 1}
                        min={0}
                        max={8}
                        onChange={(v) => setStyle('borderWidth', v)}
                    />
                    <NumInput
                        label="Pad"
                        title="Cell padding"
                        value={styles.cellPadding ?? 6}
                        min={0}
                        max={40}
                        onChange={(v) => setStyle('cellPadding', v)}
                    />
                </Row>
            </PanelSection>
        </>
    );
}

/* ------------------------------------------------------------------ *
 *  Page (canvas) — preset, size, background
 * ------------------------------------------------------------------ */

/**
 * One frame's properties: its size preset, box, clip and background — the settings
 * that used to belong to "the page", now per artboard. Duplicating or deleting the
 * frame lives here too, because the frame is the selection this panel is showing.
 */
export function FrameSection({ frame, setFrame, placeFrame, onDuplicate, onDelete, angleAction = null }) {
    const presetId = matchPresetId(frame);

    // Same "constrain proportions" the shape / image blocks offer, for the frame box.
    // Lives on the frame rather than as panel state because the artboard resize
    // grips have to honour it too (see CanvasViewport).
    const aspectLocked = !!frame.lockAspect;

    // Ratio comes from the CURRENT frame each time, so repeated edits can't drift.
    // On a locked edit presetId is left to setFrame, which re-selects a preset if
    // the pair happens to land on one exactly.
    const setW = (v) => (aspectLocked
        ? setFrame(constrainCanvasSize(frame, { width: v }))
        : setFrame({ width: v, presetId: 'custom' }));
    const setH = (v) => (aspectLocked
        ? setFrame(constrainCanvasSize(frame, { height: v }))
        : setFrame({ height: v, presetId: 'custom' }));

    const applyPreset = (id) => {
        const preset = SIZE_PRESETS.find((p) => p.id === id);
        if (!preset) return;
        setFrame({ width: preset.width, height: preset.height, presetId: preset.id });
        requestAnimationFrame(() => {
            const vp = document.querySelector('[data-workspace="1"]');
            if (!vp) return;
            const rect = vp.getBoundingClientRect();
            usePrintEditorStore.getState().fitToView(rect.width, rect.height, { padding: 72 });
        });
    };

    return (
        // Untitled: the panel header right above already names the frame, so a
        // section title here would just print it twice.
        <PanelSection>
            <div className="flex gap-1 mb-2">
                <button
                    type="button"
                    title="Duplicate this frame and everything on it (Cmd/Ctrl+Shift+D, or Alt-drag its name on the canvas)"
                    onClick={onDuplicate}
                    className={`flex-1 h-7 flex items-center justify-center gap-1.5 rounded-[5px] border ${T.border}
                        text-[11px] font-medium ${T.text} ${T.hoverBg}`}
                >
                    <IconCopyPlus size={13} />
                    Duplicate frame
                </button>
                <button
                    type="button"
                    title="Delete this frame and everything on it"
                    onClick={onDelete}
                    className={`h-7 w-7 shrink-0 flex items-center justify-center rounded-[5px] border ${T.border}
                        text-[#f24822] ${T.hoverBg}`}
                >
                    <IconTrash size={13} />
                </button>
            </div>
            <RowLabel>Size preset</RowLabel>
            <div className="mb-2">
                <FigSelect
                    value={presetId}
                    onChange={applyPreset}
                    options={SIZE_PRESETS.map((p) => ({
                        value: p.id,
                        label: `${p.label} · ${p.width}×${p.height}`,
                    }))}
                />
            </div>
            <div className="flex items-center gap-1 mb-2">
                <NumInput label="W" value={frame.width} min={CANVAS_MIN} max={CANVAS_MAX} onChange={setW} className="flex-1" />
                <NumInput label="H" value={frame.height} min={CANVAS_MIN} max={CANVAS_MAX} onChange={setH} className="flex-1" />
                <IconBtn
                    icon={aspectLocked ? IconLink : IconLinkOff}
                    label={aspectLocked
                        ? 'Unlock aspect ratio — resize the frame freely'
                        : 'Lock aspect ratio — W and H stay in proportion'}
                    active={aspectLocked}
                    onClick={() => setFrame({ lockAspect: !aspectLocked })}
                />
            </div>
            <Row>
                <NumInput label="X" value={frame.x} min={-20000} max={20000} onChange={(v) => placeFrame({ x: v })} />
                <NumInput label="Y" value={frame.y} min={-20000} max={20000} onChange={(v) => placeFrame({ y: v })} />
            </Row>
            <div className="mb-2">
                <FigCheck
                    label="Clip content"
                    checked={!!frame.clipContent}
                    onChange={(v) => setFrame({ clipContent: v })}
                />
            </div>
            <RowLabel>Background</RowLabel>
            <FillControls
                styles={{
                    backgroundType: frame.backgroundType || 'solid',
                    // The frame's solid colour lives in `background`, not `backgroundColor`
                    backgroundColor: frame.background,
                    gradientColor1: frame.gradientColor1,
                    gradientColor2: frame.gradientColor2,
                    gradientAngle: frame.gradientAngle,
                    gradientCenterX: frame.gradientCenterX,
                    gradientCenterY: frame.gradientCenterY,
                }}
                // Map the solid key back to `background`; gradient keys pass through
                setStyle={(key, value) => setFrame({ [key === 'backgroundColor' ? 'background' : key]: value })}
                angleAction={angleAction}
                solidKey="backgroundColor"
                solidDefault="#ffffff"
                allowEmptySolid={false}
            />
        </PanelSection>
    );
}

/**
 * The document's frame list, shown when nothing is selected — the way into a frame
 * that has scrolled off screen, and where new frames are added.
 */
export function FramesSection({ frames, selectedFrameId, onSelect, onAdd, onRemove }) {
    const selected = frames.find((f) => f.id === selectedFrameId) || null;
    return (
        <PanelSection
            title="Frames"
            actions={(
                <>
                    <IconBtn
                        icon={IconMinus}
                        label={selected
                            ? `Delete ${selected.name} and everything on it`
                            : 'Select a frame to delete it'}
                        disabled={!selected}
                        onClick={() => selected && onRemove(selected.id)}
                    />
                    <IconBtn icon={IconPlus} label="Add frame (F)" onClick={onAdd} />
                </>
            )}
        >
            <div className="flex flex-col gap-0.5">
                {frames.map((f) => {
                    const active = f.id === selectedFrameId;
                    return (
                        <div
                            key={f.id}
                            className={`group flex items-center gap-1.5 h-7 pl-1.5 pr-0.5 rounded-[5px] text-[11px]
                                ${active ? 'bg-[#0d99ff]/10' : T.hoverBg}`}
                        >
                            <button
                                type="button"
                                onClick={() => onSelect(f.id)}
                                className={`flex-1 min-w-0 flex items-center gap-1.5 text-left
                                    ${active ? 'text-[#0d99ff]' : T.text}`}
                            >
                                <IconFrame size={13} stroke={1.75} className="shrink-0" />
                                <span className="flex-1 min-w-0 truncate font-medium">{f.name}</span>
                                <span className={`tabular-nums text-[10px] ${T.textFaint}`}>
                                    {f.width}×{f.height}
                                </span>
                            </button>
                            {/* Per-row delete, so a frame can go without selecting it first */}
                            <IconBtn
                                icon={IconMinus}
                                label={`Delete ${f.name} and everything on it`}
                                className="opacity-0 group-hover:opacity-100 focus:opacity-100"
                                onClick={() => onRemove(f.id)}
                            />
                        </div>
                    );
                })}
            </div>
            {frames.length ? (
                <p className={`mt-1.5 text-[10px] leading-snug ${T.textFaint}`}>
                    Every frame exports as its own PNG, and all of them together as one
                    multi-page PDF. Drag a frame by its name on canvas to move it.
                </p>
            ) : (
                <p className={`text-[10px] leading-snug ${T.textFaint}`}>
                    No frames. Use + (or press F) to add one — the next element you insert
                    will create one too.
                </p>
            )}
        </PanelSection>
    );
}

/* ------------------------------------------------------------------ *
 *  Gallery thumbnail (admin)
 * ------------------------------------------------------------------ */

export function ThumbnailSection({
    thumbnailPreview, onThumbnailUpload, onThumbnailDelete, onThumbnailUseDesign,
    // The picture the thumbnail was cut from — cropping always re-cuts THIS, so
    // re-cropping never stacks up JPEG losses and can widen the rect back out.
    thumbnailSource = null,
    // { crop, rotation, ratioId, custom } from the last crop, so the dialog reopens
    // on the admin's own rectangle instead of a fresh one.
    thumbnailCrop = null,
    onThumbnailCrop,
    cropping = false,
    // Which artboard "use design" captures — the selected frame, else the first one.
    // Named in the button so it is never a guess which design is being grabbed.
    frame = null,
}) {
    const [cropOpen, setCropOpen] = useState(false);
    const cropSrc = thumbnailSource || thumbnailPreview;
    // Dropping onto the preview is the same act as picking a file with Upload —
    // it goes through the identical handler, which compresses whatever it is
    // given (a File, or a data URL for an image dragged out of a web page).
    const [dragOver, setDragOver] = useState(false);
    const [dropError, setDropError] = useState('');
    /**
     * An empty preview box is also a big Upload button.
     *
     * "Drop an image here" is the only affordance a mouse-only user has no way to
     * act on — nothing to drag from — so the obvious click has to work too. Only
     * while it is EMPTY: once a thumbnail is in there the box is a preview, and a
     * stray click on it must not open a file dialog.
     */
    const emptyPickerRef = useRef(null);

    const onDropThumbnail = async (e) => {
        e.preventDefault();
        setDragOver(false);
        setDropError('');
        const source = readImageTransfer(e.dataTransfer);
        if (!source) {
            setDropError('That drop had no image in it.');
            return;
        }
        if (source.files?.length) {
            onThumbnailUpload(source.files[0]);
            return;
        }
        // Dragged from another page or app: a link or inline SVG, which has to be
        // fetched and embedded before it can be compressed.
        try {
            const [payload] = await imageSourceToPayloads(source);
            if (payload) onThumbnailUpload(payload.src);
        } catch (err) {
            setDropError(err?.message || "Couldn't read that image.");
        }
    };

    if (typeof onThumbnailUpload !== 'function') return null;
    return (
        <PanelSection
            title="Thumbnail"
            actions={typeof onThumbnailCrop === 'function' ? (
                <button
                    type="button"
                    title={cropSrc ? 'Crop and set the ratio' : 'Add a thumbnail first'}
                    aria-label="Crop thumbnail"
                    disabled={!cropSrc || cropping}
                    onClick={() => setCropOpen(true)}
                    className={`h-6 w-6 shrink-0 flex items-center justify-center rounded-[5px]
                        ${T.textSoft} ${T.hoverBg} hover:!text-black dark:hover:!text-white
                        disabled:opacity-30 disabled:pointer-events-none`}
                >
                    {cropping ? <IconLoader2 size={13} className="animate-spin" /> : <IconCrop size={13} />}
                </button>
            ) : null}
        >
            <div
                onDragEnter={(e) => { e.preventDefault(); setDragOver(true); }}
                onDragOver={(e) => {
                    // Both handlers must preventDefault or the browser takes the drop
                    // and navigates away from the editor.
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'copy';
                    if (!dragOver) setDragOver(true);
                }}
                // Moving between the box and its own children fires dragleave too —
                // only a leave that actually left the box counts.
                onDragLeave={(e) => {
                    if (!e.currentTarget.contains(e.relatedTarget)) setDragOver(false);
                }}
                onDrop={onDropThumbnail}
                onClick={() => { if (!thumbnailPreview) emptyPickerRef.current?.click(); }}
                role={!thumbnailPreview ? 'button' : undefined}
                tabIndex={!thumbnailPreview ? 0 : undefined}
                onKeyDown={(e) => {
                    if (thumbnailPreview) return;
                    if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault();
                        emptyPickerRef.current?.click();
                    }
                }}
                title={thumbnailPreview
                    ? 'Drop an image here to replace it, or use Upload'
                    : 'Click to choose an image, or drop one here'}
                className={`rounded-[6px] border overflow-hidden mb-2 bg-[#f5f5f5] dark:bg-[#1e1e1e] transition-colors
                    outline-none focus-visible:border-[#0d99ff] focus-visible:ring-1 focus-visible:ring-[#0d99ff]
                    ${thumbnailPreview ? '' : 'cursor-pointer hover:border-[#0d99ff]/60'}
                    ${dragOver ? 'border-[#0d99ff] ring-1 ring-[#0d99ff]' : T.border}`}
            >
                <div className="aspect-[7/5] flex items-center justify-center pointer-events-none">
                    {dragOver ? (
                        <span className="text-[10px] font-medium text-[#0d99ff] px-3 text-center">
                            Drop to use as thumbnail
                        </span>
                    ) : thumbnailPreview ? (
                        // `contain`, not `cover`: after a crop the shape IS the point, and
                        // gallery cards show the thumbnail at its own ratio. Filling this
                        // fixed 7:5 box would hide exactly what was just cropped away.
                        <img src={thumbnailPreview} alt="Thumbnail preview" className="w-full h-full object-contain" />
                    ) : (
                        <span className={`flex flex-col items-center gap-1 text-[10px] ${T.textSoft} px-3 text-center`}>
                            <IconPhotoUp size={16} stroke={1.75} className={T.textFaint} />
                            Click to choose an image, or drop one here
                        </span>
                    )}
                </div>
                <input
                    ref={emptyPickerRef}
                    type="file"
                    accept="image/*"
                    className="hidden"
                    onChange={(e) => {
                        const file = e.target.files?.[0];
                        e.target.value = ''; // picking the same file again must still fire
                        if (file) onThumbnailUpload(file);
                    }}
                />
            </div>
            {dropError && (
                <p className="mb-2 text-[10px] leading-snug text-[#f24822]">{dropError}</p>
            )}
            <div className="flex gap-1 mb-1.5">
                <label className="flex-1 h-7 flex items-center justify-center gap-1.5 rounded-[5px] text-[11px] font-medium bg-[#0d99ff] text-white cursor-pointer hover:bg-[#0b87e0]">
                    <IconPhotoUp size={13} />
                    Upload
                    <input
                        type="file"
                        accept="image/*"
                        className="hidden"
                        onChange={(e) => {
                            const file = e.target.files?.[0];
                            e.target.value = '';
                            if (file) onThumbnailUpload(file);
                        }}
                    />
                </label>
                {typeof onThumbnailDelete === 'function' && (
                    <button
                        type="button"
                        title="Delete thumbnail"
                        disabled={!thumbnailPreview}
                        onClick={onThumbnailDelete}
                        className={`h-7 w-7 shrink-0 flex items-center justify-center rounded-[5px] border ${T.border}
                            text-[#f24822] ${T.hoverBg} disabled:opacity-30 disabled:pointer-events-none`}
                    >
                        <IconTrash size={13} />
                    </button>
                )}
            </div>
            {typeof onThumbnailUseDesign === 'function' && (
                <button
                    type="button"
                    disabled={!frame}
                    title={frame
                        ? `Capture ${frame.name} as the gallery thumbnail`
                        : 'Add a frame to capture a thumbnail'}
                    onClick={() => onThumbnailUseDesign(frame?.id || null)}
                    className={`w-full h-7 flex items-center justify-center gap-1.5 rounded-[5px] border ${T.border}
                        text-[11px] font-medium ${T.text} ${T.hoverBg}
                        disabled:opacity-40 disabled:pointer-events-none`}
                >
                    <IconCamera size={13} />
                    {frame ? `Use ${frame.name} design as thumbnail` : 'Use design as thumbnail'}
                </button>
            )}
            <p className={`mt-1.5 text-[10px] leading-snug ${T.textFaint}`}>
                Shown on gallery cards. Drop or upload an image, or capture a frame —
                select another frame to capture that one instead. Crop sets the ratio.
            </p>
            {typeof onThumbnailCrop === 'function' && (
                <ThumbnailCropDialog
                    open={cropOpen && !!cropSrc}
                    src={cropSrc}
                    initialCrop={thumbnailCrop?.crop || null}
                    initialRatioId={thumbnailCrop?.ratioId || 'free'}
                    initialCustom={thumbnailCrop?.custom || null}
                    initialRotation={thumbnailCrop?.rotation || 0}
                    frame={frame}
                    busy={cropping}
                    onClose={() => setCropOpen(false)}
                    onApply={async (result) => {
                        await onThumbnailCrop(result);
                        setCropOpen(false);
                    }}
                />
            )}
        </PanelSection>
    );
}

/* ------------------------------------------------------------------ *
 *  Export
 * ------------------------------------------------------------------ */

export function ExportSection({ onExport, disabled = false, label = 'Export PNG + PDF' }) {
    if (!onExport) return null;
    return (
        <PanelSection title="Export" className="border-b-0">
            <button
                type="button"
                disabled={disabled}
                onClick={onExport}
                className={`w-full h-7 flex items-center justify-center gap-1.5 rounded-[5px] border ${T.border}
                    text-[11px] font-medium ${T.text} ${T.hoverBg} disabled:opacity-40`}
            >
                <IconFileDownload size={13} />
                {label}
            </button>
        </PanelSection>
    );
}
