import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type PointerEvent as ReactPointerEvent,
  type WheelEvent as ReactWheelEvent,
} from "react";
import {
  Canvas,
  Circle,
  FabricImage,
  Line,
  PencilBrush,
  Rect,
  Textbox,
  Triangle,
  type FabricObject,
} from "fabric";
import type { ToolUiProps } from "../../core/plugins/plugin-types";
import { Button } from "../../components/ui/Button";
import { Dropzone } from "../../components/ui/Dropzone";
import { Select } from "../../components/ui/Select";
import { Slider } from "../../components/ui/Slider";
import { ToolResultPanel } from "../../components/tool/ToolResultPanel";
import { useToolExecution } from "../../components/tool/useToolExecution";
import type { ImageEditorOptions } from "./index";

type ToolMode = "select" | "draw" | "pan";
type ShapeKind = "rect" | "circle" | "triangle" | "line";

interface EditorState {
  selectedType: string;
  fill: string;
  stroke: string;
  fontSize: number;
  brushWidth: number;
  mode: ToolMode;
  zoom: number;
}

interface LayerRow {
  object: FabricObject;
  id: string;
  label: string;
}

const DEFAULT_WIDTH = 960;
const DEFAULT_HEIGHT = 640;
const HISTORY_LIMIT = 40;

const FONT_OPTIONS = [
  { label: "Inter", value: "Inter, system-ui, sans-serif" },
  { label: "Georgia", value: "Georgia, serif" },
  { label: "Mono", value: "ui-monospace, SFMono-Regular, Menlo, monospace" },
  { label: "Arial", value: "Arial, sans-serif" },
];

const FORMAT_OPTIONS = [
  { label: "PNG", value: "image/png" },
  { label: "JPEG", value: "image/jpeg" },
  { label: "WebP", value: "image/webp" },
];

// On small screens the control panels collapse into a tabbed bottom dock.
type MobileTab = "style" | "layers" | "export";
const MOBILE_TABS: { id: MobileTab; label: string }[] = [
  { id: "style", label: "Style" },
  { id: "layers", label: "Layers" },
  { id: "export", label: "Export" },
];

function objectType(object: FabricObject | undefined): string {
  if (!object) return "None";
  if (object.type === "textbox" || object.type === "i-text") return "Text";
  if (object.type === "path") return "Drawing";
  return object.type ? object.type.charAt(0).toUpperCase() + object.type.slice(1) : "Object";
}

function useElementSize<T extends HTMLElement>() {
  const ref = useRef<T | null>(null);
  const [size, setSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    if (!ref.current) return;

    const observer = new ResizeObserver(([entry]) => {
      setSize({
        width: entry.contentRect.width,
        height: entry.contentRect.height,
      });
    });

    observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);

  return { ref, size };
}

export default function ImageEditorUi({
  plugin,
}: ToolUiProps<ImageEditorOptions>) {
  const canvasElRef = useRef<HTMLCanvasElement | null>(null);
  const canvasRef = useRef<Canvas | null>(null);
  const historyRef = useRef<string[]>([]);
  const historyIndexRef = useRef(-1);
  const restoringRef = useRef(false);
  const sourceFileRef = useRef<File | undefined>(undefined);
  const isPanningRef = useRef(false);
  const panStartRef = useRef({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 });
  // Once the user zooms manually we stop auto-fitting on every resize,
  // otherwise mobile viewport reflows keep snapping the zoom back to "fit".
  const userZoomedRef = useRef(false);
  const { ref: workspaceRef, size: workspaceSize } = useElementSize<HTMLDivElement>();

  const [sourceFile, setSourceFile] = useState<File | undefined>();
  const [canvasReady, setCanvasReady] = useState(false);
  const [canvasSize, setCanvasSize] = useState({
    width: DEFAULT_WIDTH,
    height: DEFAULT_HEIGHT,
  });
  const [activeObject, setActiveObject] = useState<FabricObject | undefined>();
  const [canUndo, setCanUndo] = useState(false);
  const [canRedo, setCanRedo] = useState(false);
  const [layerRows, setLayerRows] = useState<LayerRow[]>([]);
  const [exportFormat, setExportFormat] =
    useState<ImageEditorOptions["format"]>("image/png");
  const [quality, setQuality] = useState(92);
  const [fontFamily, setFontFamily] = useState(FONT_OPTIONS[0].value);
  const [mobileTab, setMobileTab] = useState<MobileTab>("style");
  const [state, setState] = useState<EditorState>({
    selectedType: "None",
    fill: "#ffffff",
    stroke: "#1a1714",
    fontSize: 48,
    brushWidth: 8,
    mode: "select",
    zoom: 1,
  });

  const { run, result, isExecuting, error } = useToolExecution(plugin);

  const refreshHistoryControls = useCallback(() => {
    setCanUndo(historyIndexRef.current > 0);
    setCanRedo(historyIndexRef.current < historyRef.current.length - 1);
  }, []);

  const refreshLayerRows = useCallback(() => {
    const canvas = canvasRef.current;
    const rows = (canvas?.getObjects() ?? [])
      .filter((object) => object.selectable !== false)
      .map((object, index) => ({
        object,
        id: `${object.type}-${index}`,
        label: `${objectType(object)} ${index + 1}`,
      }))
      .reverse();
    setLayerRows(rows);
  }, []);

  const updateActiveState = useCallback(() => {
    const canvas = canvasRef.current;
    const object = canvas?.getActiveObject();
    setActiveObject(object);
    setState((prev) => ({
      ...prev,
      selectedType: objectType(object),
      fill: typeof object?.get("fill") === "string" ? String(object.get("fill")) : prev.fill,
      stroke: typeof object?.get("stroke") === "string" ? String(object.get("stroke")) : prev.stroke,
      fontSize: typeof object?.get("fontSize") === "number" ? Number(object.get("fontSize")) : prev.fontSize,
    }));
    refreshLayerRows();
  }, [refreshLayerRows]);

  const pushHistory = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas || restoringRef.current) return;

    const snapshot = JSON.stringify(canvas.toJSON());
    const current = historyRef.current[historyIndexRef.current];
    if (snapshot === current) return;

    const nextHistory = historyRef.current.slice(0, historyIndexRef.current + 1);
    nextHistory.push(snapshot);
    if (nextHistory.length > HISTORY_LIMIT) {
      nextHistory.shift();
    }
    historyRef.current = nextHistory;
    historyIndexRef.current = nextHistory.length - 1;
    refreshHistoryControls();
    refreshLayerRows();
  }, [refreshHistoryControls, refreshLayerRows]);

  const loadHistory = useCallback(async (index: number) => {
    const canvas = canvasRef.current;
    const snapshot = historyRef.current[index];
    if (!canvas || !snapshot) return;

    restoringRef.current = true;
    await canvas.loadFromJSON(snapshot);
    canvas.renderAll();
    restoringRef.current = false;
    historyIndexRef.current = index;
    updateActiveState();
    refreshHistoryControls();
    refreshLayerRows();
  }, [refreshHistoryControls, refreshLayerRows, updateActiveState]);

  const applyDisplayZoom = useCallback((
    zoom: number,
    size = canvasSize
  ) => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const nextZoom = Math.max(0.12, Math.min(3, zoom));
    const displayWidth = Math.max(1, Math.round(size.width * nextZoom));
    const displayHeight = Math.max(1, Math.round(size.height * nextZoom));

    canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
    canvas.setDimensions({
      width: `${displayWidth}px`,
      height: `${displayHeight}px`,
    }, {
      cssOnly: true,
    });
    canvas.calcOffset();
    setState((prev) => ({ ...prev, zoom: nextZoom }));
    canvas.requestRenderAll();
  }, [canvasSize]);

  const fitCanvasToWorkspace = useCallback(() => {
    if (workspaceSize.width === 0 || workspaceSize.height === 0) return;

    // Fit re-establishes the auto-fit baseline (resets manual-zoom lock).
    userZoomedRef.current = false;

    const margin = workspaceSize.width < 480 ? 24 : 56;
    const zoom = Math.max(0.12, Math.min(
      1,
      (workspaceSize.width - margin) / canvasSize.width,
      (workspaceSize.height - margin) / canvasSize.height
    ));

    applyDisplayZoom(zoom);
  }, [applyDisplayZoom, canvasSize.height, canvasSize.width, workspaceSize.height, workspaceSize.width]);

  const setCanvasZoom = useCallback((zoom: number) => {
    // Manual zoom — stop auto-fit from overriding the user on resize.
    userZoomedRef.current = true;
    applyDisplayZoom(zoom);
  }, [applyDisplayZoom]);

  useEffect(() => {
    if (!canvasElRef.current || canvasRef.current) return;

    const canvas = new Canvas(canvasElRef.current, {
      width: DEFAULT_WIDTH,
      height: DEFAULT_HEIGHT,
      backgroundColor: "#ffffff",
      preserveObjectStacking: true,
      selection: true,
    });

    canvas.freeDrawingBrush = new PencilBrush(canvas);
    canvas.freeDrawingBrush.color = "#1a1714";
    canvas.freeDrawingBrush.width = 8;
    canvasRef.current = canvas;

    const handleModified = () => {
      updateActiveState();
      pushHistory();
    };

    canvas.on("selection:created", updateActiveState);
    canvas.on("selection:updated", updateActiveState);
    canvas.on("selection:cleared", updateActiveState);
    canvas.on("object:modified", handleModified);
    canvas.on("object:added", pushHistory);
    canvas.on("object:removed", pushHistory);
    canvas.on("path:created", pushHistory);

    pushHistory();
    setCanvasReady(true);

    return () => {
      canvas.dispose();
      canvasRef.current = null;
    };
  }, [pushHistory, updateActiveState]);

  useEffect(() => {
    // Only auto-fit while the user hasn't taken manual control of the zoom.
    if (userZoomedRef.current) return;
    fitCanvasToWorkspace();
  }, [fitCanvasToWorkspace]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas?.freeDrawingBrush) return;

    canvas.isDrawingMode = state.mode === "draw";
    canvas.selection = state.mode === "select";
    canvas.skipTargetFind = state.mode === "pan";
    canvas.freeDrawingBrush.color = state.stroke;
    canvas.freeDrawingBrush.width = state.brushWidth;
  }, [state.brushWidth, state.mode, state.stroke]);

  const loadImageFile = useCallback(async (file: File) => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    sourceFileRef.current = file;
    setSourceFile(file);

    const url = URL.createObjectURL(file);
    try {
      const image = await FabricImage.fromURL(url);
      const maxWidth = 1400;
      const maxHeight = 1000;
      const scale = Math.min(1, maxWidth / image.width, maxHeight / image.height);
      const width = Math.max(320, Math.round(image.width * scale));
      const height = Math.max(240, Math.round(image.height * scale));

      canvas.clear();
      canvas.setDimensions({ width, height });
      setCanvasSize({ width, height });
      canvas.backgroundColor = "#ffffff";
      canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);

      image.set({
        left: 0,
        top: 0,
        originX: "left",
        originY: "top",
        selectable: false,
        evented: false,
        scaleX: width / image.width,
        scaleY: height / image.height,
      });
      canvas.add(image);
      canvas.sendObjectToBack(image);
      canvas.renderAll();

      historyRef.current = [];
      historyIndexRef.current = -1;
      pushHistory();
      refreshHistoryControls();
      refreshLayerRows();
      // New image — fit it fresh and re-enable auto-fit.
      userZoomedRef.current = false;
      const margin = workspaceSize.width > 0 && workspaceSize.width < 480 ? 24 : 56;
      const zoom = Math.max(0.12, Math.min(
        1,
        workspaceSize.width > 0 ? (workspaceSize.width - margin) / width : 1,
        workspaceSize.height > 0 ? (workspaceSize.height - margin) / height : 1
      ));
      applyDisplayZoom(zoom, { width, height });
    } finally {
      URL.revokeObjectURL(url);
    }
  }, [applyDisplayZoom, pushHistory, refreshHistoryControls, refreshLayerRows, workspaceSize.height, workspaceSize.width]);

  const applyToActive = useCallback((props: Record<string, unknown>) => {
    const canvas = canvasRef.current;
    const object = canvas?.getActiveObject();
    if (!canvas || !object) return;

    object.set(props);
    object.setCoords();
    canvas.requestRenderAll();
    updateActiveState();
    pushHistory();
  }, [pushHistory, updateActiveState]);

  const addText = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const text = new Textbox("Edit text", {
      left: 80,
      top: 80,
      width: 320,
      fontSize: state.fontSize,
      fontFamily,
      fill: state.stroke,
      backgroundColor: "rgba(255,255,255,0)",
    });
    canvas.add(text);
    canvas.setActiveObject(text);
    canvas.requestRenderAll();
    updateActiveState();
    refreshLayerRows();
  }, [fontFamily, refreshLayerRows, state.fontSize, state.stroke, updateActiveState]);

  const addShape = useCallback((kind: ShapeKind) => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const common = {
      left: 110,
      top: 110,
      fill: kind === "line" ? "" : state.fill,
      stroke: state.stroke,
      strokeWidth: 4,
    };

    const object =
      kind === "rect"
        ? new Rect({ ...common, width: 180, height: 120, rx: 4, ry: 4 })
        : kind === "circle"
          ? new Circle({ ...common, radius: 72 })
          : kind === "triangle"
            ? new Triangle({ ...common, width: 160, height: 140 })
            : new Line([120, 120, 320, 120], {
                stroke: state.stroke,
                strokeWidth: 5,
              });

    canvas.add(object);
    canvas.setActiveObject(object);
    canvas.requestRenderAll();
    updateActiveState();
    refreshLayerRows();
  }, [refreshLayerRows, state.fill, state.stroke, updateActiveState]);

  const deleteActive = useCallback(() => {
    const canvas = canvasRef.current;
    const objects = canvas?.getActiveObjects() ?? [];
    if (!canvas || objects.length === 0) return;

    objects.forEach((object) => {
      if (object.selectable !== false) canvas.remove(object);
    });
    canvas.discardActiveObject();
    canvas.requestRenderAll();
    updateActiveState();
    refreshLayerRows();
  }, [refreshLayerRows, updateActiveState]);

  const duplicateActive = useCallback(async () => {
    const canvas = canvasRef.current;
    const object = canvas?.getActiveObject();
    if (!canvas || !object) return;

    const clone = await object.clone();
    clone.set({
      left: (clone.left ?? 0) + 24,
      top: (clone.top ?? 0) + 24,
      evented: true,
    });
    canvas.add(clone);
    canvas.setActiveObject(clone);
    canvas.requestRenderAll();
    updateActiveState();
    refreshLayerRows();
  }, [refreshLayerRows, updateActiveState]);

  const moveLayer = useCallback((direction: "forward" | "backward") => {
    const canvas = canvasRef.current;
    const object = canvas?.getActiveObject();
    if (!canvas || !object) return;

    if (direction === "forward") {
      canvas.bringObjectForward(object);
    } else {
      canvas.sendObjectBackwards(object);
    }
    canvas.requestRenderAll();
    pushHistory();
    refreshLayerRows();
  }, [pushHistory, refreshLayerRows]);

  const clearObjects = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    canvas.getObjects().forEach((object) => {
      if (object.selectable !== false) canvas.remove(object);
    });
    canvas.discardActiveObject();
    canvas.requestRenderAll();
    updateActiveState();
    refreshLayerRows();
  }, [refreshLayerRows, updateActiveState]);

  const exportImage = useCallback(async () => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const dataUrl = canvas.toDataURL({
      format: exportFormat.replace("image/", "") as "png" | "jpeg" | "webp",
      quality: quality / 100,
      multiplier: 1,
    });

    await run(
      { files: sourceFileRef.current ? [sourceFileRef.current] : undefined },
      { format: exportFormat, quality, dataUrl }
    );
  }, [exportFormat, quality, run]);

  const handleKeyDown = useCallback((event: KeyboardEvent) => {
    const target = event.target as HTMLElement | null;
    const isTyping = target?.tagName === "INPUT" || target?.tagName === "TEXTAREA";
    if (isTyping) return;

    if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z") {
      event.preventDefault();
      const nextCanRedo = historyIndexRef.current < historyRef.current.length - 1;
      const nextCanUndo = historyIndexRef.current > 0;
      if (event.shiftKey && nextCanRedo) {
        void loadHistory(historyIndexRef.current + 1);
      } else if (nextCanUndo) {
        void loadHistory(historyIndexRef.current - 1);
      }
      return;
    }

    if ((event.key === "Delete" || event.key === "Backspace") && activeObject) {
      event.preventDefault();
      deleteActive();
    }
  }, [activeObject, deleteActive, loadHistory]);

  useEffect(() => {
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [handleKeyDown]);

  const handleWorkspacePointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
    if (state.mode !== "pan") return;
    const workspace = workspaceRef.current;
    if (!workspace) return;

    isPanningRef.current = true;
    panStartRef.current = {
      x: event.clientX,
      y: event.clientY,
      scrollLeft: workspace.scrollLeft,
      scrollTop: workspace.scrollTop,
    };
    workspace.setPointerCapture(event.pointerId);
  }, [state.mode, workspaceRef]);

  const handleWorkspacePointerMove = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
    if (!isPanningRef.current) return;
    const workspace = workspaceRef.current;
    if (!workspace) return;

    const start = panStartRef.current;
    workspace.scrollLeft = start.scrollLeft - (event.clientX - start.x);
    workspace.scrollTop = start.scrollTop - (event.clientY - start.y);
  }, [workspaceRef]);

  const handleWorkspacePointerUp = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
    if (!isPanningRef.current) return;
    isPanningRef.current = false;
    workspaceRef.current?.releasePointerCapture(event.pointerId);
  }, [workspaceRef]);

  const handleWorkspaceWheel = useCallback((event: ReactWheelEvent<HTMLDivElement>) => {
    if (!event.ctrlKey && !event.metaKey) return;
    event.preventDefault();
    const delta = event.deltaY > 0 ? -0.08 : 0.08;
    setCanvasZoom(state.zoom + delta);
  }, [setCanvasZoom, state.zoom]);

  // Control panels are declared once and placed either in the desktop
  // sidebar or the mobile tabbed dock — the single <canvas> is never cloned.
  const propertiesPanel = (
    <section className="flex flex-col gap-3">
      <div className="font-mono text-[10px] uppercase tracking-wider text-[var(--p-muted)]">
        properties
      </div>
      <div className="rounded-[12px] border border-[var(--p-border)] bg-[var(--p-surface)] p-3">
        <div className="mb-3 font-sans text-sm font-semibold text-[var(--p-text)]">
          {state.selectedType}
        </div>
        <div className="grid grid-cols-2 gap-3">
          <label className="flex flex-col gap-1 font-sans text-[12px] text-[var(--p-muted)]">
            Fill
            <input
              type="color"
              value={state.fill}
              onChange={(event) => {
                setState((prev) => ({ ...prev, fill: event.target.value }));
                applyToActive({ fill: event.target.value });
              }}
              className="h-9 w-full rounded border border-[var(--p-border)] bg-transparent"
            />
          </label>
          <label className="flex flex-col gap-1 font-sans text-[12px] text-[var(--p-muted)]">
            Stroke / text
            <input
              type="color"
              value={state.stroke}
              onChange={(event) => {
                setState((prev) => ({ ...prev, stroke: event.target.value }));
                applyToActive({ stroke: event.target.value, fill: activeObject?.type === "textbox" ? event.target.value : activeObject?.get("fill") });
              }}
              className="h-9 w-full rounded border border-[var(--p-border)] bg-transparent"
            />
          </label>
        </div>

        <div className="mt-3 flex flex-col gap-3">
          <Select
            label="Font"
            value={fontFamily}
            options={FONT_OPTIONS}
            onChange={(event) => {
              setFontFamily(event.target.value);
              applyToActive({ fontFamily: event.target.value });
            }}
          />
          <Slider
            label="Text size"
            min={12}
            max={160}
            value={state.fontSize}
            onChange={(event) => {
              const fontSize = Number(event.target.value);
              setState((prev) => ({ ...prev, fontSize }));
              applyToActive({ fontSize });
            }}
          />
          <Slider
            label="Brush"
            min={1}
            max={48}
            value={state.brushWidth}
            onChange={(event) => {
              setState((prev) => ({ ...prev, brushWidth: Number(event.target.value) }));
            }}
          />
        </div>
      </div>
    </section>
  );

  const arrangePanel = (
    <section className="flex flex-col gap-3">
      <div className="font-mono text-[10px] uppercase tracking-wider text-[var(--p-muted)]">
        arrange
      </div>
      <div className="grid grid-cols-2 gap-2">
        <Button variant="secondary" onClick={duplicateActive} disabled={!activeObject}>Duplicate</Button>
        <Button variant="danger" onClick={deleteActive} disabled={!activeObject}>Delete</Button>
        <Button variant="secondary" onClick={() => moveLayer("forward")} disabled={!activeObject}>Forward</Button>
        <Button variant="secondary" onClick={() => moveLayer("backward")} disabled={!activeObject}>Backward</Button>
        <Button variant="secondary" onClick={() => loadHistory(historyIndexRef.current - 1)} disabled={!canUndo}>Undo</Button>
        <Button variant="secondary" onClick={() => loadHistory(historyIndexRef.current + 1)} disabled={!canRedo}>Redo</Button>
      </div>
      <Button variant="secondary" onClick={clearObjects}>
        Clear editable objects
      </Button>
    </section>
  );

  const layersPanel = (
    <section className="flex min-h-0 flex-col gap-3">
      <div className="font-mono text-[10px] uppercase tracking-wider text-[var(--p-muted)]">
        layers
      </div>
      <div className="max-h-[160px] overflow-y-auto rounded-[12px] border border-[var(--p-border)] bg-[var(--p-surface)] p-2">
        {layerRows.length === 0 ? (
          <div className="px-2 py-4 text-center text-sm text-[var(--p-muted)]">
            No editable layers yet
          </div>
        ) : (
          layerRows.map((row) => (
            <button
              key={row.id}
              onClick={() => {
                canvasRef.current?.setActiveObject(row.object);
                canvasRef.current?.requestRenderAll();
                updateActiveState();
              }}
              className={`mb-1 w-full rounded-lg px-3 py-2 text-left font-sans text-[13px] ${
                activeObject === row.object
                  ? "bg-[var(--p-accent)] text-[var(--p-accent-ink)]"
                  : "bg-[var(--p-chip)] text-[var(--p-text)]"
              }`}
            >
              {row.label}
            </button>
          ))
        )}
      </div>
    </section>
  );

  const exportPanel = (
    <section className="flex flex-col gap-3 lg:mt-auto">
      <div className="font-mono text-[10px] uppercase tracking-wider text-[var(--p-muted)]">
        export
      </div>
      <Select
        label="Format"
        value={exportFormat}
        options={FORMAT_OPTIONS}
        onChange={(event) => setExportFormat(event.target.value as ImageEditorOptions["format"])}
      />
      <Slider
        label="Quality"
        min={10}
        max={100}
        value={quality}
        onChange={(event) => setQuality(Number(event.target.value))}
      />
      <Button onClick={exportImage} disabled={!canvasReady || isExecuting}>
        {isExecuting ? "Exporting..." : "Export image"}
      </Button>
      {sourceFile && (
        <div className="truncate font-mono text-[10px] text-[var(--p-muted)]">
          source: {sourceFile.name}
        </div>
      )}
      {error && <div className="text-sm text-red-700">{error}</div>}
      {result && <ToolResultPanel result={result} />}
    </section>
  );

  return (
    <div className="flex h-full min-h-[460px] flex-col bg-[var(--p-surface)]">
      <div className="flex shrink-0 flex-col gap-2 border-b border-[var(--p-border)] px-3 py-2.5 lg:flex-row lg:flex-wrap lg:items-center lg:gap-2 lg:px-4 lg:py-3">
        <Dropzone
          accept="image/jpeg,image/png,image/webp"
          multiple={false}
          maxSizeMb={25}
          onFilesDrop={(files) => {
            if (files[0]) void loadImageFile(files[0]);
          }}
          className="min-h-0 w-full p-2.5 sm:w-[240px]"
        />

        <div className="flex gap-1.5 overflow-x-auto no-scrollbar -mx-3 px-3 lg:mx-0 lg:px-0 lg:flex-wrap lg:overflow-visible">
          <Button
            className="shrink-0"
            variant={state.mode === "select" ? "primary" : "secondary"}
            onClick={() => setState((prev) => ({ ...prev, mode: "select" }))}
          >
            Select
          </Button>
          <Button
            className="shrink-0"
            variant={state.mode === "draw" ? "primary" : "secondary"}
            onClick={() => setState((prev) => ({ ...prev, mode: "draw" }))}
          >
            Draw
          </Button>
          <Button
            className="shrink-0"
            variant={state.mode === "pan" ? "primary" : "secondary"}
            onClick={() => setState((prev) => ({ ...prev, mode: "pan" }))}
          >
            Pan
          </Button>
          <Button className="shrink-0" variant="secondary" onClick={addText}>Text</Button>
          <Button className="shrink-0" variant="secondary" onClick={() => addShape("rect")}>Rect</Button>
          <Button className="shrink-0" variant="secondary" onClick={() => addShape("circle")}>Circle</Button>
          <Button className="shrink-0" variant="secondary" onClick={() => addShape("triangle")}>Triangle</Button>
          <Button className="shrink-0" variant="secondary" onClick={() => addShape("line")}>Line</Button>
        </div>

        <div className="flex items-center gap-1.5 lg:ml-auto">
          <Button className="shrink-0" variant="secondary" onClick={() => setCanvasZoom(state.zoom - 0.12)}>
            -
          </Button>
          <button
            onClick={fitCanvasToWorkspace}
            className="shrink-0 rounded-[10px] border border-[var(--p-border)] bg-[var(--p-chip)] px-3 py-2 font-mono text-[12px] text-[var(--p-text)]"
          >
            {Math.round(state.zoom * 100)}%
          </button>
          <Button className="shrink-0" variant="secondary" onClick={() => setCanvasZoom(state.zoom + 0.12)}>
            +
          </Button>
          <Button className="shrink-0" variant="secondary" onClick={() => setCanvasZoom(1)}>
            100%
          </Button>
        </div>
      </div>

      <div className="flex min-h-0 flex-1 flex-col lg:flex-row">
        <div
          ref={workspaceRef}
          onPointerDown={handleWorkspacePointerDown}
          onPointerMove={handleWorkspacePointerMove}
          onPointerUp={handleWorkspacePointerUp}
          onPointerCancel={handleWorkspacePointerUp}
          onWheel={handleWorkspaceWheel}
          className={`min-h-[200px] flex-1 overflow-auto bg-[var(--p-bg)] p-4 lg:min-h-0 lg:p-6 ${
            state.mode === "pan" ? "cursor-grab active:cursor-grabbing" : ""
          }`}
        >
          <div className="flex min-h-full min-w-max items-center justify-center">
            <div
              className="shrink-0 border border-[var(--p-border)] bg-white shadow-[0_24px_60px_-36px_var(--p-shadow-soft)]"
              style={{
                width: canvasSize.width * state.zoom,
                height: canvasSize.height * state.zoom,
              }}
            >
              <canvas ref={canvasElRef} />
            </div>
          </div>
        </div>

        <aside className="flex min-h-0 shrink-0 flex-col border-t border-[var(--p-border)] bg-[var(--p-surface-2)] lg:w-[300px] lg:border-l lg:border-t-0">
          <div className="flex gap-1 border-b border-[var(--p-border)] p-1.5 lg:hidden">
            {MOBILE_TABS.map((t) => (
              <button
                key={t.id}
                onClick={() => setMobileTab(t.id)}
                className={`flex-1 rounded-lg px-3 py-2 font-sans text-[13px] font-medium transition-colors ${
                  mobileTab === t.id
                    ? "bg-[var(--p-accent)] text-[var(--p-accent-ink)]"
                    : "text-[var(--p-muted)] hover:text-[var(--p-text)]"
                }`}
              >
                {t.label}
              </button>
            ))}
          </div>

          <div className="flex min-h-0 max-h-[44vh] flex-col gap-4 overflow-y-auto p-4 pb-[max(1rem,env(safe-area-inset-bottom))] lg:max-h-none lg:flex-1 lg:pb-4">
            <div className={`${mobileTab === "style" ? "flex flex-col gap-4" : "hidden"} lg:contents`}>
              {propertiesPanel}
            </div>
            <div className={`${mobileTab === "layers" ? "flex flex-col gap-4" : "hidden"} lg:contents`}>
              {arrangePanel}
              {layersPanel}
            </div>
            <div className={`${mobileTab === "export" ? "flex flex-col gap-4" : "hidden"} lg:contents`}>
              {exportPanel}
            </div>
          </div>
        </aside>
      </div>
    </div>
  );
}
