import { Button, Popover, Tooltip, useToast } from '@/Components/ui';
import {
    type DevicePresetDto,
    type FactorScreenshotGaps,
    type FormFactor,
    type FrameLayer,
} from '@/types/layout';
import { router, usePage } from '@inertiajs/react';
import type { LocaleDto, SharedProps } from '@/types/inertia';
import {
    AlignCenterHorizontal,
    AlignCenterVertical,
    AlignEndHorizontal,
    AlignEndVertical,
    AlignHorizontalSpaceAround,
    AlignStartHorizontal,
    AlignStartVertical,
    AlignVerticalSpaceAround,
    Award,
    Check,
    ChevronDown,
    Circle,
    Hexagon,
    Crosshair,
    Ellipse,
    Image as ImageIcon,
    Link2,
    Link2Off,
    Loader2,
    Lock,
    Palette,
    PenTool,
    Redo2,
    Shapes,
    Smartphone,
    Sparkles,
    Square,
    Star as StarIcon,
    Type,
    Undo2,
    type LucideIcon,
} from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { cn } from '@/lib/cn';
import {
    createBackgroundLayer,
    createFrameLayer,
    createImageLayer,
    createShapeLayer,
    createTextLayer,
} from './factories';
import { ELEMENT_CATALOG, ELEMENTS, materializeElement } from './elements';
import { FormFactorSwitcher } from './FormFactorSwitcher';
import { uploadMedia } from './media';
import { useEditor } from './store';

type Props = {
    projectId: number;
    devicePresets?: DevicePresetDto[];
    activeScreenId: number;
    /** Project's active factor (URL-driven via `?factor=`). Drives the
     *  FramePickerPanel's platform partition (iPhone / iPad → Apple
     *  devices; Android phone / tablet → Android devices). */
    formFactor: FormFactor;
    /** Full set of factors the project ships. >1 surfaces the
     *  FormFactorSwitcher pills inline next to the locale picker. */
    activeFormFactors?: FormFactor[];
    factorScreenshotGaps?: FactorScreenshotGaps;
    /** Initial state of the sync-new-layers project flag — toggle button
     *  reflects this and PATCHes the server on click. Available on every
     *  project (blank or kit-copied) so the user can flip the workflow at
     *  any time. */
    syncNewLayers?: boolean;
    /** Concurrency lock (Team Workspaces P4 / Δ21). When read-only the
     *  editing affordances are disabled and a banner replaces the tool row. */
    readOnly?: boolean;
    /** Viewer (role-based read-only). Distinct from lock contention — renders a
     *  "View-only" review bar that KEEPS the locale + factor switchers (pure
     *  navigation) so a viewer can review every variant, but no editing tools
     *  and no take-over (viewers never acquire the lock). */
    viewerMode?: boolean;
    /** The current lock holder (someone else editing), when read-only. */
    lockHolder?: { id: number; name: string } | null;
    /** Whether the caller may take over (allowed to edit + not the holder). */
    canTakeOver?: boolean;
    /** Force-acquire the lock + re-hydrate. */
    onTakeOver?: () => void;
    /**
     * When true, a Sparkles button appears in the toolbar that opens the
     * AI chat panel. Passed from the Editor page — the page prop
     * `aiEnabled` (from ProjectController::show) controls visibility.
     * NOTE: If ProjectController::show doesn't yet pass `aiEnabled`, the
     * button simply stays hidden (prop defaults false).
     */
    aiEnabled?: boolean;
    /** Whether the AI panel is open (owned by the Editor page so the panel
     *  renders as a flex sibling, not a fixed overlay). */
    aiPanelOpen?: boolean;
    /** Toggle the AI panel open/closed. */
    onToggleAiPanel?: () => void;
};

export default function EditorToolbar({
    projectId,
    devicePresets = [],
    activeScreenId,
    formFactor,
    activeFormFactors,
    factorScreenshotGaps,
    syncNewLayers,
    readOnly = false,
    viewerMode = false,
    lockHolder,
    canTakeOver = false,
    onTakeOver,
    aiEnabled = false,
    aiPanelOpen = false,
    onToggleAiPanel,
}: Props) {
    const addLayer = useEditor((s) => s.addLayer);
    const addLayers = useEditor((s) => s.addLayers);
    const alignSelected = useEditor((s) => s.alignSelected);
    const distributeSelected = useEditor((s) => s.distributeSelected);
    const undo = useEditor((s) => s.undo);
    const redo = useEditor((s) => s.redo);
    const past = useEditor((s) => s.past);
    const future = useEditor((s) => s.future);
    const selectionCount = useEditor((s) => s.selection.length);
    const canvas = useEditor((s) => s.layout.canvas);
    const penMode = useEditor((s) => s.penMode);
    const enterPenMode = useEditor((s) => s.enterPenMode);
    const exitPenMode = useEditor((s) => s.exitPenMode);
    const calloutMode = useEditor((s) => s.calloutMode);
    const enterCalloutMode = useEditor((s) => s.enterCalloutMode);
    const exitCalloutMode = useEditor((s) => s.exitCalloutMode);

    const fileInputRef = useRef<HTMLInputElement>(null);
    const [uploading, setUploading] = useState(false);
    const toast = useToast();

    const pickFile = () => {
        fileInputRef.current?.click();
    };

    const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0];
        e.target.value = '';
        if (!file) return;

        setUploading(true);
        const toastId = toast.show({
            kind: 'loading',
            message: `Uploading ${file.name}`,
            progress: 0,
        });
        try {
            const media = await uploadMedia(projectId, file, {
                onProgress: (fraction) => toast.update(toastId, { progress: fraction }),
            });
            addLayer(
                createImageLayer(
                    {
                        storagePath: media.storagePath,
                        width: media.width,
                        height: media.height,
                        fileName: file.name,
                    },
                    canvas,
                ),
            );
            toast.dismiss(toastId);
            toast.success(`Uploaded ${file.name}`);
        } catch (err) {
            toast.dismiss(toastId);
            toast.error(err instanceof Error ? err.message : 'Upload failed');
        } finally {
            setUploading(false);
        }
    };

    // Viewer (role-based read-only): a slim "View-only" review bar that keeps
    // the locale + factor switchers (pure navigation — a viewer reviews every
    // variant) but no editing tools, no sync toggle, no take-over.
    if (viewerMode) {
        return (
            <div
                className="flex h-9 shrink-0 items-center gap-2 border-b border-border bg-muted px-3"
                data-testid="editor-readonly-banner"
            >
                <span
                    className="flex items-center gap-1.5 text-[12px] font-medium text-muted-foreground"
                    data-testid="editor-viewonly-banner"
                >
                    <Lock className="h-3.5 w-3.5" strokeWidth={2} />
                    View-only access
                </span>
                <div className="ml-auto flex items-center gap-2">
                    <LocaleSwitcher
                        projectId={projectId}
                        activeScreenId={activeScreenId}
                    />
                    {activeFormFactors && activeFormFactors.length > 0 && (
                        <FormFactorSwitcher
                            activeFormFactors={activeFormFactors}
                            activeFactor={formFactor}
                            screenshotGaps={factorScreenshotGaps}
                        />
                    )}
                </div>
            </div>
        );
    }

    // Read-only mode (Team Workspaces P4 / Δ21): another editor holds the
    // lock. Replace the tool row with a banner. The take-over button only
    // shows when the caller is allowed to edit (a viewer never sees it).
    if (readOnly) {
        const holderName = lockHolder?.name ?? 'Someone';
        return (
            <div
                className="flex h-9 shrink-0 items-center justify-between gap-2 border-b border-border bg-muted px-3"
                data-testid="editor-readonly-banner"
            >
                <span className="flex items-center gap-1.5 text-[12px] font-medium text-muted-foreground">
                    <Lock className="h-3.5 w-3.5" strokeWidth={2} />
                    {holderName} is editing · read-only
                </span>
                {canTakeOver && (
                    <button
                        type="button"
                        onClick={onTakeOver}
                        data-testid="editor-take-over"
                        className="inline-flex h-7 items-center gap-1.5 rounded-md border border-border bg-card px-2.5 text-[12px] font-medium text-foreground transition-colors hover:bg-muted"
                    >
                        Take over
                    </button>
                )}
            </div>
        );
    }

    return (
        <div className="flex h-9 shrink-0 items-center gap-1 border-b border-border bg-card px-2">
            <input
                ref={fileInputRef}
                type="file"
                accept="image/*"
                className="hidden"
                onChange={handleFile}
                data-testid="upload-file-input"
            />

            <ToolButton
                icon={Palette}
                label="Add background"
                testId="add-background"
                onClick={() => addLayer(createBackgroundLayer())}
            />
            <ToolButton
                icon={Type}
                label="Add text"
                testId="add-text"
                onClick={() => addLayer(createTextLayer())}
            />
            <Popover
                align="start"
                trigger={({ open, toggle }) => (
                    <Tooltip label="Add shape">
                        <Button
                            size="icon"
                            variant="ghost"
                            onClick={toggle}
                            data-testid="add-shape"
                            aria-label="Add shape"
                            aria-expanded={open}
                            className={cn(
                                'h-7 w-7',
                                (open || penMode) && 'bg-muted text-foreground',
                            )}
                        >
                            <Shapes className="h-3.5 w-3.5" strokeWidth={2} />
                        </Button>
                    </Tooltip>
                )}
            >
                <div className="grid grid-cols-3 gap-0.5">
                    <PopoverAction
                        icon={Square}
                        label="Rectangle"
                        testId="add-rect"
                        onClick={() => addLayer(createShapeLayer('rect'))}
                    />
                    <PopoverAction
                        icon={Circle}
                        label="Ellipse"
                        testId="add-ellipse"
                        onClick={() => addLayer(createShapeLayer('ellipse'))}
                    />
                    {/* Line + Arrow removed from the shape picker — both
                        are stroke-only primitives that are easier to draw
                        accurately with the Pen tool (or via a thin rect
                        for line). Factory + renderer kept so any existing
                        layouts with `shape: 'line' | 'arrow'` still load
                        and edit normally. */}
                    <PopoverAction
                        icon={Hexagon}
                        label="Polygon"
                        testId="add-polygon"
                        onClick={() => addLayer(createShapeLayer('polygon'))}
                    />
                    <PopoverAction
                        icon={StarIcon}
                        label="Star"
                        testId="add-star"
                        onClick={() => addLayer(createShapeLayer('star'))}
                    />
                    <PopoverAction
                        icon={PenTool}
                        label="Pen"
                        testId="add-pen"
                        active={penMode}
                        onClick={() => {
                            if (penMode) exitPenMode();
                            else enterPenMode();
                        }}
                    />
                </div>
            </Popover>

            <Popover
                align="start"
                trigger={({ open, toggle }) => (
                    <Tooltip label="Add element">
                        <Button
                            size="icon"
                            variant="ghost"
                            onClick={toggle}
                            data-testid="add-element"
                            aria-label="Add element"
                            aria-expanded={open}
                            className={cn('h-7 w-7', open && 'bg-muted text-foreground')}
                        >
                            <Award className="h-3.5 w-3.5" strokeWidth={2} />
                        </Button>
                    </Tooltip>
                )}
            >
                <div className="flex w-44 flex-col gap-0.5">
                    {ELEMENT_CATALOG.map((el) => {
                        const Icon = ELEMENTS[el.key]?.icon ?? Award;
                        return (
                            <button
                                key={el.key}
                                type="button"
                                onClick={() => {
                                    const ls = materializeElement(el.key, {}, { canvasWidth: canvas.width });
                                    if (ls.length > 0) addLayers(ls, { selectId: ls[0].id });
                                }}
                                data-testid={`add-element-${el.key}`}
                                className="flex items-center gap-2 rounded-md px-2 py-1.5 text-left text-[12px] text-foreground transition-colors hover:bg-muted"
                            >
                                <Icon className="h-3.5 w-3.5 shrink-0 text-muted-foreground" strokeWidth={2} />
                                {el.label}
                            </button>
                        );
                    })}
                </div>
            </Popover>

            <ToolButton
                icon={uploading ? Loader2 : ImageIcon}
                iconClassName={uploading ? 'animate-spin' : ''}
                label={uploading ? 'Uploading image' : 'Add image'}
                testId="add-image"
                disabled={uploading}
                onClick={pickFile}
            />

            <Popover
                align="start"
                trigger={({ open, toggle }) => (
                    <Tooltip label="Add device frame">
                        <Button
                            size="icon"
                            variant="ghost"
                            onClick={toggle}
                            disabled={devicePresets.length === 0}
                            data-testid="add-frame"
                            aria-label="Add device frame"
                            aria-expanded={open}
                            className={cn('h-7 w-7', open && 'bg-muted text-foreground')}
                        >
                            <Smartphone className="h-3.5 w-3.5" strokeWidth={2} />
                        </Button>
                    </Tooltip>
                )}
            >
                <FramePickerPanel
                    devicePresets={devicePresets}
                    projectFormFactor={formFactor}
                    multiFactor={(activeFormFactors?.length ?? 1) > 1}
                    onPick={(preset, style, syncAcrossFactors) => {
                        const styleOverrides: Partial<FrameLayer> =
                            style === 'real'
                                ? {
                                      realFrameEnabled: true,
                                      ...(preset.real_frame_color_options?.[0]?.slug
                                          ? {
                                                realFrameColor:
                                                    preset.real_frame_color_options[0].slug,
                                            }
                                          : {}),
                                  }
                                : {};
                        // Bake the sync choice into the new layer. Default
                        // (omitted) is treated as true server-side; we store
                        // `false` explicitly when the user opts out so the
                        // intent survives autosave + stamp.
                        const syncOverride: Partial<FrameLayer> = syncAcrossFactors
                            ? {}
                            : { linkDeviceFamily: false };
                        addLayer(createFrameLayer(preset, canvas, { ...styleOverrides, ...syncOverride }));
                    }}
                />
            </Popover>

            <Popover
                align="start"
                trigger={({ open, toggle }) => (
                    <Tooltip label="Add callout">
                        <Button
                            size="icon"
                            variant="ghost"
                            onClick={() => {
                                if (calloutMode !== 'idle') {
                                    exitCalloutMode();
                                    return;
                                }
                                toggle();
                            }}
                            data-testid="add-callout"
                            data-active={calloutMode !== 'idle' ? 'true' : undefined}
                            aria-label="Add callout"
                            aria-expanded={open}
                            aria-pressed={calloutMode !== 'idle'}
                            className={cn(
                                'h-7 w-7',
                                (open || calloutMode !== 'idle') && 'bg-muted text-foreground',
                            )}
                        >
                            <Crosshair className="h-3.5 w-3.5" strokeWidth={2} />
                        </Button>
                    </Tooltip>
                )}
            >
                <div className="grid grid-cols-3 gap-0.5">
                    <PopoverAction
                        icon={Square}
                        label="Rectangle"
                        testId="add-callout-rounded-rect"
                        onClick={() => enterCalloutMode('rounded-rect')}
                    />
                    <PopoverAction
                        icon={Circle}
                        label="Circle / pill"
                        testId="add-callout-pill"
                        onClick={() => enterCalloutMode('pill')}
                    />
                    <PopoverAction
                        icon={Ellipse}
                        label="Ellipse"
                        testId="add-callout-ellipse"
                        onClick={() => enterCalloutMode('ellipse')}
                    />
                </div>
            </Popover>

            <div className="mx-1.5 h-4 w-px bg-border" />

            <ToolButton
                icon={Undo2}
                label="Undo"
                shortcut="⌘Z"
                testId="undo"
                onClick={undo}
                disabled={past.length === 0}
            />
            <ToolButton
                icon={Redo2}
                label="Redo"
                shortcut="⇧⌘Z"
                testId="redo"
                onClick={redo}
                disabled={future.length === 0}
            />

            <div className="mx-1.5 h-4 w-px bg-border" />

            <Popover
                align="start"
                trigger={({ open, toggle }) => (
                    <Tooltip label="Align & distribute">
                        <Button
                            size="icon"
                            variant="ghost"
                            onClick={toggle}
                            disabled={selectionCount < 2}
                            data-testid="align-menu"
                            aria-label="Align & distribute"
                            aria-expanded={open}
                            className={cn('h-7 w-7', open && 'bg-muted text-foreground')}
                        >
                            <AlignCenterHorizontal className="h-3.5 w-3.5" strokeWidth={2} />
                        </Button>
                    </Tooltip>
                )}
            >
                <div className="flex flex-col gap-1">
                    <div className="grid grid-cols-3 gap-0.5">
                        <PopoverAction
                            icon={AlignStartVertical}
                            label="Align left"
                            testId="align-left"
                            onClick={() => alignSelected('left')}
                            disabled={selectionCount < 2}
                        />
                        <PopoverAction
                            icon={AlignCenterVertical}
                            label="Align center"
                            testId="align-center-h"
                            onClick={() => alignSelected('centerH')}
                            disabled={selectionCount < 2}
                        />
                        <PopoverAction
                            icon={AlignEndVertical}
                            label="Align right"
                            testId="align-right"
                            onClick={() => alignSelected('right')}
                            disabled={selectionCount < 2}
                        />
                        <PopoverAction
                            icon={AlignStartHorizontal}
                            label="Align top"
                            testId="align-top"
                            onClick={() => alignSelected('top')}
                            disabled={selectionCount < 2}
                        />
                        <PopoverAction
                            icon={AlignCenterHorizontal}
                            label="Align middle"
                            testId="align-center-v"
                            onClick={() => alignSelected('centerV')}
                            disabled={selectionCount < 2}
                        />
                        <PopoverAction
                            icon={AlignEndHorizontal}
                            label="Align bottom"
                            testId="align-bottom"
                            onClick={() => alignSelected('bottom')}
                            disabled={selectionCount < 2}
                        />
                    </div>
                    <div className="my-1 h-px bg-border" />
                    <div className="grid grid-cols-3 gap-0.5">
                        <PopoverAction
                            icon={AlignHorizontalSpaceAround}
                            label="Distribute horizontally"
                            testId="distribute-h"
                            onClick={() => distributeSelected('horizontal')}
                            disabled={selectionCount < 3}
                        />
                        <PopoverAction
                            icon={AlignVerticalSpaceAround}
                            label="Distribute vertically"
                            testId="distribute-v"
                            onClick={() => distributeSelected('vertical')}
                            disabled={selectionCount < 3}
                        />
                    </div>
                </div>
            </Popover>

            <div className="ml-auto flex items-center gap-2">
                {aiEnabled && (
                    <Tooltip label="AI assistant" side="bottom">
                        <Button
                            size="icon"
                            variant="ghost"
                            onClick={() => onToggleAiPanel?.()}
                            data-testid="ai-chat-toggle"
                            aria-label="AI assistant"
                            aria-pressed={aiPanelOpen}
                            className={cn(
                                'h-7 w-7',
                                aiPanelOpen && 'bg-muted text-foreground',
                            )}
                        >
                            <Sparkles className="h-3.5 w-3.5" strokeWidth={2} />
                        </Button>
                    </Tooltip>
                )}
                <SyncNewLayersToggle
                    projectId={projectId}
                    initialOn={syncNewLayers === true}
                />
                <LocaleSwitcher
                    projectId={projectId}
                    activeScreenId={activeScreenId}
                />
                {activeFormFactors && activeFormFactors.length > 0 && (
                    <FormFactorSwitcher
                        activeFormFactors={activeFormFactors}
                        activeFactor={formFactor}
                        screenshotGaps={factorScreenshotGaps}
                    />
                )}
            </div>
        </div>
    );
}

type ToolButtonProps = {
    icon: LucideIcon;
    label: string;
    testId: string;
    onClick: () => void;
    disabled?: boolean;
    iconClassName?: string;
    shortcut?: string;
};

function ToolButton({
    icon: Icon,
    label,
    testId,
    onClick,
    disabled,
    iconClassName,
    shortcut,
}: ToolButtonProps) {
    return (
        <Tooltip label={label} shortcut={shortcut}>
            <Button
                size="icon"
                variant="ghost"
                onClick={onClick}
                disabled={disabled}
                data-testid={testId}
                aria-label={label}
                className="h-7 w-7"
            >
                <Icon className={`h-3.5 w-3.5 ${iconClassName ?? ''}`} strokeWidth={2} />
            </Button>
        </Tooltip>
    );
}

type PopoverActionProps = {
    icon: LucideIcon;
    label: string;
    testId: string;
    onClick: () => void;
    disabled?: boolean;
    shortcut?: string;
    active?: boolean;
};

function PopoverAction({
    icon: Icon,
    label,
    testId,
    onClick,
    disabled,
    shortcut,
    active,
}: PopoverActionProps) {
    return (
        <Tooltip label={label} shortcut={shortcut} side="bottom">
            <button
                type="button"
                onClick={onClick}
                disabled={disabled}
                data-testid={testId}
                aria-label={label}
                aria-pressed={active}
                className={`inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors disabled:pointer-events-none disabled:opacity-40 ${
                    active
                        ? 'bg-muted text-foreground'
                        : 'text-muted-foreground hover:bg-muted hover:text-foreground'
                }`}
            >
                <Icon className="h-3.5 w-3.5" strokeWidth={2} />
            </button>
        </Tooltip>
    );
}

type SyncNewLayersToggleProps = {
    projectId: number;
    initialOn: boolean;
};

/**
 * Quick toggle for `projects.sync_new_layers`. Sits in the editor
 * toolbar's right cluster so the user can flip propagation on/off
 * mid-session — e.g. ON while laying out the headlines + frames, OFF
 * when adding a per-device annotation, ON again for the next batch of
 * synced content. Available on every project regardless of blank vs
 * kit-copied origin.
 */
function SyncNewLayersToggle({
    projectId,
    initialOn,
}: SyncNewLayersToggleProps) {
    const [on, setOn] = useState(initialOn);
    const [pending, setPending] = useState(false);

    // The same flag is editable from the Project Settings modal; its save
    // refreshes Inertia props WITHOUT remounting the toolbar, so reconcile
    // local state whenever the server-backed prop changes. The optimistic
    // flip below is unaffected — a successful PATCH refreshes the prop to
    // the value we already set.
    useEffect(() => {
        setOn(initialOn);
    }, [initialOn]);

    const flip = () => {
        if (pending) return;
        const next = ! on;
        // Optimistic flip — server state matters for the NEXT screen save's
        // propagation logic; revert if the PATCH fails so the UI doesn't
        // claim a state the server doesn't have.
        setOn(next);
        setPending(true);
        router.patch(
            `/projects/${projectId}`,
            { sync_new_layers: next },
            {
                preserveScroll: true,
                preserveState: true,
                onError: () => setOn(! next),
                onFinish: () => setPending(false),
            },
        );
    };

    const Icon = on ? Link2 : Link2Off;
    return (
        <Tooltip
            label={
                on
                    ? 'Sync ON — text / shape / background / group / frame layers you add propagate to all device factors. To make a specific text layer per-device, select it and click the Linked button next to its Content.'
                    : 'Sync OFF — new layers stay on the active device only (except frames, which always family-sync). Toggle on to propagate adds across factors.'
            }
            side="bottom"
            multiline
        >
            <button
                type="button"
                onClick={flip}
                disabled={pending}
                aria-pressed={on}
                data-testid="toolbar-sync-new-layers"
                data-on={on ? 'true' : undefined}
                className={cn(
                    'inline-flex h-7 items-center gap-1.5 rounded-md border px-2 text-[11px] font-medium transition-colors disabled:opacity-60',
                    on
                        ? 'border-accent/40 bg-accent/10 text-accent hover:bg-accent/15'
                        : 'border-border bg-background text-muted-foreground hover:bg-muted/60 hover:text-foreground',
                )}
            >
                <Icon className="h-3.5 w-3.5" strokeWidth={2} />
                <span>Sync</span>
            </button>
        </Tooltip>
    );
}

type LocaleSwitcherProps = {
    projectId: number;
    activeScreenId: number;
};

function LocaleMasterPill({ side = 'bottom' }: { side?: 'top' | 'bottom' | 'left' | 'right' }) {
    return (
        <Tooltip
            label="Default locale — text on other locales falls back here when no per-locale override is set."
            side={side}
            multiline
        >
            <span className="rounded-md bg-accent/10 px-1.5 py-0.5 text-[10px] font-medium text-accent">
                Default
            </span>
        </Tooltip>
    );
}

function LocaleSwitcher({
    projectId,
    activeScreenId,
}: LocaleSwitcherProps) {
    const allLocales = usePage<SharedProps>().props.locales ?? [];
    const activeLocales = useEditor((s) => s.activeLocales);
    const defaultLocale = useEditor((s) => s.defaultLocale);
    const activeLocale = useEditor((s) => s.activeLocale);

    // Single-locale projects don't need a switcher — locked-on to default.
    if (activeLocales.length < 2) return null;

    const localesByCode = new Map(allLocales.map((l) => [l.code, l]));
    const lookup = (code: string): LocaleDto =>
        localesByCode.get(code) ?? {
            code,
            name: code.toUpperCase(),
            native_name: code.toUpperCase(),
            direction: 'ltr',
            font_stack: '',
        };
    const visible = activeLocales.map(lookup);
    const ltrVisible = visible.filter((l) => l.direction === 'ltr');
    const rtlVisible = visible.filter((l) => l.direction === 'rtl');

    const pick = (code: string) => {
        if (code === activeLocale) return;
        router.visit(
            `/editor/${projectId}?screen=${activeScreenId}&locale=${code}`,
        );
    };

    const activeRow = lookup(activeLocale);

    return (
        <Popover
            align="end"
            trigger={({ open, toggle }) => (
                <Tooltip label="Switch locale" side="left">
                    <button
                        type="button"
                        onClick={toggle}
                        data-testid="locale-switcher"
                        aria-expanded={open}
                        aria-label="Switch locale"
                        className={`inline-flex h-7 items-center gap-1.5 rounded-md border border-border px-2 text-[11px] font-medium transition-colors ${
                            open
                                ? 'bg-muted text-foreground'
                                : 'bg-background text-foreground hover:bg-muted/60'
                        }`}
                    >
                        <span dir={activeRow.direction}>{activeRow.native_name}</span>
                        <span
                            dir="ltr"
                            className="font-mono text-[10px] uppercase tracking-wide text-muted-foreground"
                        >
                            {activeRow.code}
                        </span>
                        {activeLocale === defaultLocale && <LocaleMasterPill />}
                        <ChevronDown className="h-3 w-3 text-muted-foreground" strokeWidth={2} />
                    </button>
                </Tooltip>
            )}
        >
            <div className="flex min-w-[260px] flex-col gap-0.5">
                {(
                    [
                        { key: 'ltr', label: 'LTR', locales: ltrVisible },
                        { key: 'rtl', label: 'RTL', locales: rtlVisible },
                    ] as const
                )
                    .filter((g) => g.locales.length > 0)
                    .map((group) => (
                        <div
                            key={group.key}
                            data-testid={`locale-group-${group.key}`}
                            className="flex flex-col gap-0.5"
                        >
                            <div className="px-2 pb-0.5 pt-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
                                {group.label}
                            </div>
                            {group.locales.map((locale) => {
                                const isActive = locale.code === activeLocale;
                                const isDefault = locale.code === defaultLocale;
                                return (
                                    <button
                                        key={locale.code}
                                        type="button"
                                        onClick={() => pick(locale.code)}
                                        data-testid={`locale-option-${locale.code}`}
                                        data-active={isActive ? 'true' : undefined}
                                        className={`flex items-center justify-between gap-3 rounded-md px-2 py-1.5 text-left text-xs transition-colors ${
                                            isActive
                                                ? 'bg-accent/10 text-accent'
                                                : 'text-foreground hover:bg-muted'
                                        }`}
                                    >
                                        {/* Dropdown rows stay LTR — see
                                          * matching comment in
                                          * Projects/Index.tsx LocaleAddGroup. */}
                                        <span className="inline-flex items-baseline gap-1.5">
                                            <span>{locale.native_name}</span>
                                            <span className="font-mono text-[10px] uppercase tracking-wide text-muted-foreground">
                                                · {locale.code}
                                            </span>
                                        </span>
                                        <span className="flex items-center gap-1.5">
                                            {isDefault && (
                                                <LocaleMasterPill side="left" />
                                            )}
                                            <span className="flex h-4 w-4 items-center justify-center text-muted-foreground">
                                                {isActive ? (
                                                    <Check className="h-3.5 w-3.5 text-accent" strokeWidth={2.5} />
                                                ) : null}
                                            </span>
                                        </span>
                                    </button>
                                );
                            })}
                        </div>
                    ))}
            </div>
        </Popover>
    );
}

// Brand ordering: Apple first (master factor uses iPhone), then Google,
// Samsung, OnePlus, Lenovo, Other. Within a brand we keep seed order
// (matches "newest-first" the way the migrations seed each line).
const BRAND_ORDER = ['Apple', 'Google', 'Samsung', 'OnePlus', 'Lenovo', 'Other'] as const;
type Brand = (typeof BRAND_ORDER)[number];

function brandFor(preset: DevicePresetDto): Brand {
    const k = preset.key;
    if (k.startsWith('iphone-') || k.startsWith('ipad-')) return 'Apple';
    if (k.startsWith('android-pixel')) return 'Google';
    if (k.startsWith('android-galaxy')) return 'Samsung';
    if (k.startsWith('android-oneplus')) return 'OnePlus';
    if (k.startsWith('android-lenovo')) return 'Lenovo';
    return 'Other';
}

// Strip the diagonal-size parenthetical from labels — it duplicates the
// pixel-dim column on the right and adds visual noise. Keeps useful
// parentheticals like "(M4)" or "(3rd gen)" intact.
//   "iPhone 16 Pro Max (6.9\")"       → "iPhone 16 Pro Max"
//   "iPhone SE (3rd gen, 4.7\")"      → "iPhone SE (3rd gen)"
//   "iPad Pro 13\" (M4)"              → "iPad Pro 13\" (M4)"  (inline diagonal kept)
function cleanLabel(raw: string): string {
    return raw
        .replace(/\s*\(\d+(?:\.\d+)?"\)$/, '')
        .replace(/,\s*\d+(?:\.\d+)?"\)/, ')');
}

type FrameStyle = 'real' | 'stylized';

function FramePickerPanel({
    devicePresets,
    projectFormFactor,
    multiFactor,
    onPick,
}: {
    devicePresets: DevicePresetDto[];
    /** Drives the platform partition. iPhone / iPad projects see every
     *  Apple device in the real-frame library; Android phone / tablet
     *  projects see every Android device in the real-frame library. */
    projectFormFactor: FormFactor;
    /** Project has >1 active form factor → show the "Sync across factors"
     *  toggle. Single-factor projects skip it (there's nothing to sync to). */
    multiFactor: boolean;
    onPick: (preset: DevicePresetDto, style: FrameStyle, syncAcrossFactors: boolean) => void;
}) {
    // Default to "Real" — that's the photoreal PNG render, which is the
    // primary value-add over the older stylized-only path. Stylized stays
    // selectable for the same catalog as a cheaper / no-PNG-load
    // alternative.
    const [style, setStyle] = useState<FrameStyle>('real');
    // Sync across factors defaults ON — picking iPhone 17 Pro Max here should
    // populate the iPad / Android factors with their family equivalents. User
    // can opt out (intentional per-factor diverging devices) right before
    // clicking a device.
    const [syncAcrossFactors, setSyncAcrossFactors] = useState(true);

    // Catalog = devices in the platform group that ALSO ship a
    // photorealistic frame PNG. Stylized-only devices (the wider set the
    // procedural renderer can draw but without spec-accurate radii /
    // notch / camera details) intentionally don't surface here — the
    // picker treats Real and Stylized as two renderings of the SAME
    // curated catalog, not as two different catalogs. The real-frame
    // library grows over time; expanding it is the way to add devices
    // to this picker (see `real-device-frames-plan.md`).
    // Play Store feature graphic is a Google Play marketing asset — when
    // the user wants to drop a device frame onto the banner, the picker
    // should surface every Android device (phones + tablets), not iPhones.
    const projectPlatform: 'apple' | 'android' =
        projectFormFactor.startsWith('android_')
            || projectFormFactor === 'playstore_feature_graphic'
            ? 'android'
            : 'apple';
    const catalogPresets = useMemo(
        () =>
            devicePresets.filter((p) => {
                if (!p.has_real_frame) return false;
                const isAndroid = p.form_factor.startsWith('android_');
                return projectPlatform === 'android' ? isAndroid : !isAndroid;
            }),
        [devicePresets, projectPlatform],
    );

    const hasAnyRealFrame = catalogPresets.length > 0;
    const effectiveStyle = style;

    // Both tabs use the same catalog; only the render path differs
    // (PNG vs procedural). The Real / Stylized choice flows through to
    // `onPick`, which writes `realFrameEnabled` onto the new FrameLayer.
    const visible = catalogPresets;

    const phones = visible.filter((p) => p.category === 'phone');
    const tablets = visible.filter((p) => p.category === 'tablet');

    return (
        <div className="flex max-h-[70vh] w-[320px] flex-col">
            <div
                className="flex shrink-0 items-center justify-between gap-2 px-3 py-1.5"
                // Popover auto-closes on any click inside the panel (that's how
                // pick-buttons get free dismiss). Stop propagation here so the
                // header tabs stay interactive without dismissing.
                onClick={(e) => e.stopPropagation()}
            >
                <span className="text-[11px] font-semibold text-foreground">
                    Device frames
                </span>
                {hasAnyRealFrame && (
                    <div
                        role="tablist"
                        aria-label="Frame style"
                        className="flex items-center gap-0.5"
                    >
                        {(
                            [
                                { key: 'real', label: 'Real' },
                                { key: 'stylized', label: 'Stylized' },
                            ] as const
                        ).map((opt) => (
                            <button
                                key={opt.key}
                                type="button"
                                role="tab"
                                aria-selected={effectiveStyle === opt.key}
                                onClick={() => setStyle(opt.key)}
                                data-testid={`add-frame-style-${opt.key}`}
                                className={cn(
                                    'rounded px-2 py-0.5 text-[10px] font-medium transition-colors',
                                    effectiveStyle === opt.key
                                        ? 'bg-foreground/90 text-background'
                                        : 'text-muted-foreground hover:bg-muted hover:text-foreground',
                                )}
                            >
                                {opt.label}
                            </button>
                        ))}
                    </div>
                )}
            </div>
            {multiFactor && (
                <label
                    onClick={(e) => e.stopPropagation()}
                    className="flex shrink-0 cursor-pointer items-start gap-2 border-t border-border bg-muted/30 px-3 py-2 text-[11px] hover:bg-muted/50"
                >
                    <input
                        type="checkbox"
                        checked={syncAcrossFactors}
                        onChange={(e) => setSyncAcrossFactors(e.target.checked)}
                        data-testid="add-frame-sync-across-factors"
                        className="mt-0.5 h-3.5 w-3.5 cursor-pointer accent-accent"
                    />
                    <span className="flex flex-col gap-0.5">
                        <span className="font-medium text-foreground">
                            Sync device across factors
                        </span>
                        <span className="leading-snug text-muted-foreground">
                            Picking iPhone here also picks iPad Pro, Galaxy S26
                            and Tab S8 Ultra on their factors (family equivalents).
                            Turn off to set per-factor devices independently.
                        </span>
                    </span>
                </label>
            )}
            <div className="sleek-scroll flex min-h-0 flex-1 flex-col overflow-y-auto py-1">
                <DeviceSection
                    title="Phones"
                    presets={phones}
                    onPick={(p) => onPick(p, effectiveStyle, syncAcrossFactors)}
                />
                {phones.length > 0 && tablets.length > 0 && (
                    <div className="my-1 h-px bg-border" />
                )}
                <DeviceSection
                    title="Tablets"
                    presets={tablets}
                    onPick={(p) => onPick(p, effectiveStyle, syncAcrossFactors)}
                />
            </div>
        </div>
    );
}

function DeviceSection({
    title,
    presets,
    onPick,
}: {
    title: string;
    presets: DevicePresetDto[];
    onPick: (preset: DevicePresetDto) => void;
}) {
    if (presets.length === 0) return null;

    // Group by brand, preserving BRAND_ORDER. Inside each brand keep the
    // original (seed) order — that already reads as "flagship first".
    const grouped = new Map<Brand, DevicePresetDto[]>();
    for (const p of presets) {
        const b = brandFor(p);
        const bucket = grouped.get(b) ?? [];
        bucket.push(p);
        grouped.set(b, bucket);
    }
    const orderedBrands = BRAND_ORDER.filter((b) => grouped.has(b));

    return (
        <div className="flex flex-col">
            <div className="sticky top-0 z-10 bg-card px-3 pb-1 pt-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/80">
                {title}
            </div>
            {orderedBrands.map((brand, brandIdx) => (
                <div key={brand} className="flex flex-col gap-0.5">
                    {orderedBrands.length > 1 && (
                        <div
                            className={cn(
                                'px-3 pb-0.5 pt-1 text-[10px] font-medium text-muted-foreground',
                                brandIdx > 0 && 'mt-1 border-t border-border/60 pt-1.5',
                            )}
                        >
                            {brand}
                        </div>
                    )}
                    {grouped.get(brand)!.map((preset) => (
                        <button
                            key={preset.key}
                            type="button"
                            onClick={() => onPick(preset)}
                            title={`${cleanLabel(preset.label)} — ${preset.export_w} × ${preset.export_h} px`}
                            data-testid={`add-frame-${preset.key}`}
                            className="flex min-w-0 items-center justify-between gap-3 rounded-md px-3 py-1.5 text-left text-xs text-foreground transition-colors hover:bg-muted"
                        >
                            <span className="min-w-0 flex-1 truncate">
                                {cleanLabel(preset.label)}
                            </span>
                            <span className="shrink-0 font-mono text-[10px] text-muted-foreground">
                                {preset.export_w}×{preset.export_h}
                            </span>
                        </button>
                    ))}
                </div>
            ))}
        </div>
    );
}
