"use client";

import {
  ActionIcon,
  Alert,
  Anchor,
  Badge,
  Box,
  Breadcrumbs,
  Button,
  CopyButton,
  Divider,
  Group,
  Image,
  Loader,
  Menu,
  Modal,
  Paper,
  Radio,
  Select,
  SimpleGrid,
  Stack,
  Table,
  Text,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import {
  IconAlertTriangle,
  IconArrowUp,
  IconCheck,
  IconCode,
  IconCopy,
  IconDots,
  IconFolder,
  IconFolderPlus,
  IconMail,
  IconPencil,
  IconPhoto,
  IconProgressCheck,
  IconTrash,
} from "@tabler/icons-react";
import moment from "moment";
import React, { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router";
import {
  createEDMFolder,
  deleteEDMAsset,
  deleteEDMFolderImages,
  deleteEDMFolder,
  deleteEDMTemplate,
  updateEDMFolder,
} from "@/lib/features/edm/action";
import { browseEDMFolder, listEDMFolders } from "@/lib/features/edm/query";
import { folderSelectOptions } from "@/lib/features/edm/folderTree";
import type {
  EDMBrowseResult,
  EDMFolder,
  EDMFolderAsset,
} from "@/lib/features/edm/types";
import type { AccessScope } from "@/lib/features/types";
import { WorkflowSidebar } from "@/components/WorkflowSidebar";
import {
  WorkflowStatusBadge,
  useWorkflowStatuses,
} from "@/components/WorkflowStatusBadge";
import type { WorkflowEntityType } from "@/lib/features/workflow/types";
import { EDMFolderTreePane } from "./EDMFolderTreePane";

interface Props {
  accessScope: AccessScope;
  /** Bumped by the parent after an import so the browser refetches. */
  refreshToken?: number;
}

/**
 * File-manager view of the template store.
 *
 * Shows one folder at a time — its own subfolders, its own templates, and the
 * images those templates use — rather than a tree of everything. Opening a
 * folder navigates into it, exactly like a directory listing.
 *
 * On the images section: images live under the folder's own images/ prefix in
 * the bucket, which is what lets a folder's images be listed and deleted as a
 * unit. Filenames keep a short content hash so a second hero.png cannot
 * overwrite the first — an overwrite would change an image already sitting in
 * someone's inbox.
 */
export function EDMFolderBrowser({ accessScope, refreshToken }: Props) {
  const navigate = useNavigate();

  const [folderId, setFolderId] = useState<string | null>(null);
  const [data, setData] = useState<EDMBrowseResult | null>(null);
  const [loading, setLoading] = useState(true);

  /** Flat list of every folder — only needed for the "move to" picker. */
  const [allFolders, setAllFolders] = useState<EDMFolder[]>([]);

  const [newOpened, setNewOpened] = useState(false);
  const [newName, setNewName] = useState("");
  const [saving, setSaving] = useState(false);

  const [renameTarget, setRenameTarget] = useState<EDMFolder | null>(null);
  const [renameValue, setRenameValue] = useState("");

  const [moveTarget, setMoveTarget] = useState<EDMFolder | null>(null);
  const [moveParent, setMoveParent] = useState<string | null>(null);

  const [deleteTarget, setDeleteTarget] = useState<EDMFolder | null>(null);
  const [deleteMode, setDeleteMode] = useState<"orphan" | "move" | "cascade">("orphan");
  const [deleteMoveTo, setDeleteMoveTo] = useState<string | null>(null);
  const [cascadeConfirm, setCascadeConfirm] = useState("");

  const [templateDeleteTarget, setTemplateDeleteTarget] =
    useState<{ id: string; name: string } | null>(null);
  const [assetDeleteTarget, setAssetDeleteTarget] = useState<EDMFolderAsset | null>(null);
  const [imagesWipeOpen, setImagesWipeOpen] = useState(false);

  /**
   * Which record's approval drawer is open.
   *
   * One piece of state for both folders and templates: the drawer is modal, so
   * only ever one can be open, and the entity type travels with the id because
   * the two id spaces are different (folders are uuid, templates are text).
   */
  const [workflowTarget, setWorkflowTarget] =
    useState<{ type: WorkflowEntityType; id: string } | null>(null);

  const templateWorkflow = useWorkflowStatuses("EDM_TEMPLATE");
  const folderWorkflow = useWorkflowStatuses("EDM_FOLDER");

  const load = useCallback(async (id: string | null) => {
    setLoading(true);
    try {
      const res = await browseEDMFolder(id);
      if (res.success && res.data) {
        setData(res.data);
      } else {
        notifications.show({ color: "red", message: res.message || "Failed to open folder" });
      }
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    void load(folderId);
  }, [folderId, refreshToken, load]);

  const refreshAllFolders = useCallback(async () => {
    const res = await listEDMFolders();
    if (res.success && Array.isArray(res.data)) setAllFolders(res.data);
  }, []);

  useEffect(() => {
    void refreshAllFolders();
  }, [refreshAllFolders, refreshToken]);

  const reload = async () => {
    await Promise.all([load(folderId), refreshAllFolders()]);
  };

  const handleCreate = async () => {
    if (!newName.trim()) return;
    setSaving(true);
    try {
      // Created inside whatever folder is open, so "new folder" means what it
      // does in a file manager rather than always landing at the root.
      const res = await createEDMFolder({ name: newName.trim(), parent_id: folderId });
      if (res.success) {
        setNewName("");
        setNewOpened(false);
        notifications.show({ color: "green", message: "Folder created" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Failed to create folder" });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleRename = async () => {
    if (!renameTarget || !renameValue.trim()) return;
    setSaving(true);
    try {
      const res = await updateEDMFolder(renameTarget.id, { name: renameValue.trim() });
      if (res.success) {
        setRenameTarget(null);
        notifications.show({ color: "green", message: "Folder renamed" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Rename failed" });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleMove = async () => {
    if (!moveTarget) return;
    setSaving(true);
    try {
      // The server refuses a move into the folder's own subtree; surfacing its
      // message is better than trying to pre-empt every case in the picker.
      const res = await updateEDMFolder(moveTarget.id, { parent_id: moveParent });
      if (res.success) {
        setMoveTarget(null);
        notifications.show({ color: "green", message: "Folder moved" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Move failed" });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleDelete = async () => {
    if (!deleteTarget) return;
    setSaving(true);
    try {
      const res = await deleteEDMFolder(deleteTarget.id, {
        mode: deleteMode,
        targetFolderId: deleteMoveTo,
      });
      if (res.success) {
        setDeleteTarget(null);
        setDeleteMode("orphan");
        setDeleteMoveTo(null);
        setCascadeConfirm("");
        notifications.show({ color: "green", message: res.message || "Folder deleted" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Delete failed" });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleImagesWipe = async () => {
    if (!folderId) return;
    setSaving(true);
    try {
      const res = await deleteEDMFolderImages(folderId);
      if (res.success) {
        setImagesWipeOpen(false);
        notifications.show({ color: "green", message: res.message || "Images deleted" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Delete failed" });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleAssetDelete = async () => {
    if (!assetDeleteTarget) return;
    setSaving(true);
    try {
      const res = await deleteEDMAsset(assetDeleteTarget.path);
      if (res.success) {
        setAssetDeleteTarget(null);
        notifications.show({ color: "green", message: res.message || "Image deleted" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Delete failed" });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleTemplateDelete = async () => {
    if (!templateDeleteTarget) return;
    setSaving(true);
    try {
      const res = await deleteEDMTemplate(templateDeleteTarget.id);
      if (res.success) {
        setTemplateDeleteTarget(null);
        notifications.show({ color: "green", message: "Template deleted" });
        await reload();
      } else {
        notifications.show({ color: "red", message: res.message || "Delete failed" });
      }
    } finally {
      setSaving(false);
    }
  };

  const parentId = data?.breadcrumb.length
    ? (data.breadcrumb[data.breadcrumb.length - 2]?.id ?? null)
    : null;

  /**
   * Bucket prefix the folder's images actually sit in.
   *
   * Taken from the stored paths rather than rebuilt from the folder name: the
   * path records where a file was uploaded, so a folder renamed since then still
   * has its images under the old prefix. Null when they disagree, which is the
   * honest answer.
   */
  const imagesPrefix = React.useMemo(() => {
    const dirs = new Set(
      (data?.assets ?? []).map((a) => a.path.split("/").slice(0, -1).join("/")),
    );
    return dirs.size === 1 ? [...dirs][0] : null;
  }, [data]);

  const isEmpty =
    !loading &&
    data &&
    data.folders.length === 0 &&
    data.templates.length === 0 &&
    data.assets.length === 0;

  return (
    <Group align="flex-start" gap="md" wrap="nowrap">
      {/*
        Tree on the left, one folder's contents on the right — the same shape as
        a file manager, and the reason is the same: the listing answers "what is
        in here", the tree answers "where is here".
      */}
      <Box style={{ width: 240, minWidth: 240 }} visibleFrom="md">
        <EDMFolderTreePane
          folders={allFolders}
          activeId={folderId}
          onSelect={setFolderId}
          statuses={folderWorkflow.statuses}
        />
      </Box>

      <Stack gap="md" style={{ flex: 1, minWidth: 0 }}>
      {/* ── path bar ── */}
      <Group
        justify="space-between"
        style={{
          backgroundColor: "var(--mantine-color-gray-0)",
          padding: "10px 14px",
          borderRadius: 12,
        }}
      >
        <Group gap="xs">
          {data && data.breadcrumb.length > 0 && (
            <Tooltip label="Up one level">
              <ActionIcon variant="subtle" onClick={() => setFolderId(parentId)}>
                <IconArrowUp size={16} />
              </ActionIcon>
            </Tooltip>
          )}
          <Breadcrumbs separator="›">
            <Anchor
              size="sm"
              fw={folderId === null ? 600 : 400}
              onClick={() => setFolderId(null)}
            >
              All templates
            </Anchor>
            {data?.breadcrumb.map((b, i) => (
              <Anchor
                key={b.id}
                size="sm"
                fw={i === (data.breadcrumb.length - 1) ? 600 : 400}
                onClick={() => setFolderId(b.id)}
              >
                {b.name}
              </Anchor>
            ))}
          </Breadcrumbs>
          {loading && <Loader size="xs" />}
        </Group>

        <Group gap="xs">
          <Button
            size="xs"
            variant="light"
            color="indigo"
            leftSection={<IconCode size={14} />}
            onClick={() =>
              navigate(
                folderId
                  ? `/admin/edm/workspace?folder=${folderId}`
                  : "/admin/edm/workspace",
              )
            }
          >
            Open in workspace
          </Button>
          {accessScope.create && (
            <Button
              size="xs"
              variant="light"
              leftSection={<IconFolderPlus size={14} />}
              onClick={() => setNewOpened(true)}
            >
              New folder here
            </Button>
          )}
        </Group>
      </Group>

      {isEmpty && (
        <Stack align="center" py="xl" gap={4}>
          <IconFolder size={40} opacity={0.25} />
          <Text c="dimmed" size="sm">This folder is empty.</Text>
        </Stack>
      )}

      {/* ── folders ── */}
      {data && data.folders.length > 0 && (
        <Box>
          <Text size="xs" fw={600} c="dimmed" mb={6}>
            FOLDERS ({data.folders.length})
          </Text>
          <SimpleGrid cols={{ base: 1, sm: 2, md: 3, lg: 4 }} spacing="sm">
            {data.folders.map((f) => (
              <Paper
                key={f.id}
                withBorder
                p="sm"
                radius="md"
                style={{ cursor: "pointer" }}
                onClick={() => setFolderId(f.id)}
              >
                <Group justify="space-between" wrap="nowrap">
                  <Group gap="xs" wrap="nowrap" style={{ minWidth: 0 }}>
                    <IconFolder size={20} color="var(--mantine-color-blue-6)" />
                    <Text size="sm" fw={500} truncate>{f.name}</Text>
                    <WorkflowStatusBadge status={folderWorkflow.statuses[f.id]} size="xs" />
                  </Group>

                  {(accessScope.update || accessScope.delete) && (
                    <Menu withinPortal position="bottom-end">
                      <Menu.Target>
                        <ActionIcon
                          variant="subtle"
                          color="gray"
                          onClick={(e) => e.stopPropagation()}
                        >
                          <IconDots size={16} />
                        </ActionIcon>
                      </Menu.Target>
                      <Menu.Dropdown onClick={(e) => e.stopPropagation()}>
                        <Menu.Item
                          leftSection={<IconCode size={14} />}
                          onClick={() => navigate(`/admin/edm/workspace?folder=${f.id}`)}
                        >
                          Open in workspace
                        </Menu.Item>
                        <Menu.Item
                          leftSection={<IconProgressCheck size={14} />}
                          onClick={() =>
                            setWorkflowTarget({ type: "EDM_FOLDER", id: f.id })
                          }
                        >
                          Approval &amp; comments
                        </Menu.Item>
                        <Menu.Divider />
                        {accessScope.update && (
                          <>
                            <Menu.Item
                              leftSection={<IconPencil size={14} />}
                              onClick={() => { setRenameTarget(f); setRenameValue(f.name); }}
                            >
                              Rename
                            </Menu.Item>
                            <Menu.Item
                              leftSection={<IconFolder size={14} />}
                              onClick={() => { setMoveTarget(f); setMoveParent(f.parent_id); }}
                            >
                              Move to…
                            </Menu.Item>
                          </>
                        )}
                        {accessScope.delete && (
                          <>
                            <Menu.Divider />
                            <Menu.Item
                              color="red"
                              leftSection={<IconTrash size={14} />}
                              onClick={() => setDeleteTarget(f)}
                            >
                              Delete
                            </Menu.Item>
                          </>
                        )}
                      </Menu.Dropdown>
                    </Menu>
                  )}
                </Group>
              </Paper>
            ))}
          </SimpleGrid>
        </Box>
      )}

      {/* ── templates ── */}
      {data && data.templates.length > 0 && (
        <Box>
          <Divider mb="sm" />
          <Text size="xs" fw={600} c="dimmed" mb={6}>
            TEMPLATES ({data.templates.length})
          </Text>
          <Table striped highlightOnHover withTableBorder>
            <Table.Thead>
              <Table.Tr>
                <Table.Th>Name</Table.Th>
                <Table.Th>Template ID</Table.Th>
                <Table.Th>Subject</Table.Th>
                <Table.Th>Status</Table.Th>
                <Table.Th>Approval</Table.Th>
                <Table.Th>Updated</Table.Th>
                <Table.Th style={{ width: 50 }} />
              </Table.Tr>
            </Table.Thead>
            <Table.Tbody>
              {data.templates.map((t) => (
                <Table.Tr
                  key={t.id}
                  style={{ cursor: "pointer" }}
                  onClick={() => navigate(`/admin/edm/${t.id}`)}
                >
                  <Table.Td>
                    <Group gap={8} wrap="nowrap">
                      <IconMail size={16} color="var(--mantine-color-grape-6)" />
                      <Text size="sm" fw={500}>{t.name}</Text>
                    </Group>
                  </Table.Td>
                  <Table.Td>
                    {/* The id is what every other system references this
                        template by, so it needs to be copyable without opening
                        the template. */}
                    <Group gap={4} wrap="nowrap">
                      <Text size="xs" ff="monospace" c="dimmed">{t.id}</Text>
                      <CopyButton value={t.id}>
                        {({ copied, copy }) => (
                          <Tooltip label={copied ? "Copied" : "Copy ID"}>
                            <ActionIcon
                              size="xs"
                              variant="subtle"
                              color={copied ? "teal" : "gray"}
                              onClick={(e) => { e.stopPropagation(); copy(); }}
                            >
                              {copied ? <IconCheck size={12} /> : <IconCopy size={12} />}
                            </ActionIcon>
                          </Tooltip>
                        )}
                      </CopyButton>
                    </Group>
                  </Table.Td>
                  <Table.Td>
                    <Text size="sm" c={t.subject ? undefined : "dimmed"} fs={t.subject ? undefined : "italic"}>
                      {t.subject || "no subject"}
                    </Text>
                  </Table.Td>
                  <Table.Td>
                    <Group gap={4} wrap="nowrap">
                      {/* No active version means nothing was ever published, so
                          the mail worker has nothing to render — usually a batch
                          import that hit a missing image. */}
                      {t.active_version_id ? (
                        <Badge size="sm" color="teal" variant="light">published</Badge>
                      ) : (
                        <Badge size="sm" color="orange" variant="light">unpublished</Badge>
                      )}
                      {t.source === "sendgrid" && (
                        <Badge size="sm" color="gray" variant="light">SendGrid</Badge>
                      )}
                    </Group>
                  </Table.Td>
                  <Table.Td>
                    {templateWorkflow.statuses[t.id] ? (
                      <WorkflowStatusBadge status={templateWorkflow.statuses[t.id]} />
                    ) : (
                      <Text size="xs" c="dimmed">not started</Text>
                    )}
                  </Table.Td>
                  <Table.Td>
                    <Text size="xs" c="dimmed">
                      {moment(t.updated_at).format("DD MMM YYYY")}
                    </Text>
                  </Table.Td>
                  <Table.Td>
                    <Group gap={2} wrap="nowrap">
                      <Tooltip label="Approval & comments">
                        <ActionIcon
                          variant="subtle"
                          color="gray"
                          onClick={(e) => {
                            e.stopPropagation();
                            setWorkflowTarget({ type: "EDM_TEMPLATE", id: t.id });
                          }}
                        >
                          <IconProgressCheck size={15} />
                        </ActionIcon>
                      </Tooltip>
                      <Tooltip label={accessScope.update ? "Edit HTML" : "View"}>
                        <ActionIcon
                          variant="subtle"
                          color="gray"
                          onClick={(e) => {
                            e.stopPropagation();
                            navigate(`/admin/edm/${t.id}`);
                          }}
                        >
                          <IconPencil size={15} />
                        </ActionIcon>
                      </Tooltip>
                      {accessScope.delete && (
                        <Tooltip label="Delete template">
                          <ActionIcon
                            variant="subtle"
                            color="red"
                            onClick={(e) => {
                              e.stopPropagation();
                              setTemplateDeleteTarget({ id: t.id, name: t.name });
                            }}
                          >
                            <IconTrash size={15} />
                          </ActionIcon>
                        </Tooltip>
                      )}
                    </Group>
                  </Table.Td>
                </Table.Tr>
              ))}
            </Table.Tbody>
          </Table>
        </Box>
      )}

      {/* ── images ── */}
      {data && data.assets.length > 0 && (
        <Box>
          <Divider mb="sm" />
          <Group gap="xs" mb={6}>
            <Text size="xs" fw={600} c="dimmed">
              IMAGES ({data.assets.length})
            </Text>
            {/*
              The actual bucket prefix, taken from the objects themselves rather
              than rebuilt from the folder name — the stored path records where a
              file was uploaded, and a folder renamed since then still has its
              images under the old prefix. Showing a guess would be worse than
              showing nothing.
            */}
            {imagesPrefix && (
              <Group gap={2} wrap="nowrap">
                <Text size="xs" c="dimmed" ff="monospace" truncate maw={320}>
                  {imagesPrefix}
                </Text>
                <CopyButton value={imagesPrefix}>
                  {({ copied, copy }) => (
                    <Tooltip label={copied ? "Copied" : "Copy storage prefix"}>
                      <ActionIcon
                        size="xs"
                        variant="subtle"
                        color={copied ? "teal" : "gray"}
                        onClick={copy}
                      >
                        {copied ? <IconCheck size={12} /> : <IconCopy size={12} />}
                      </ActionIcon>
                    </Tooltip>
                  )}
                </CopyButton>
              </Group>
            )}
            {accessScope.delete && folderId && (
              <Button
                size="compact-xs"
                variant="subtle"
                color="red"
                leftSection={<IconTrash size={12} />}
                onClick={() => setImagesWipeOpen(true)}
              >
                Delete images folder
              </Button>
            )}
          </Group>
          <SimpleGrid cols={{ base: 2, sm: 3, md: 5, lg: 6 }} spacing="sm">
            {data.assets.map((a) => (
              <AssetCard
                key={a.path}
                asset={a}
                onDelete={accessScope.delete ? () => setAssetDeleteTarget(a) : undefined}
              />
            ))}
          </SimpleGrid>
        </Box>
      )}

      {/* ── modals ── */}
      <Modal opened={newOpened} onClose={() => setNewOpened(false)} title="New folder" centered size="sm">
        <Stack>
          <TextInput
            label="Folder name"
            placeholder="e.g. Campaigns 2026"
            value={newName}
            onChange={(e) => setNewName(e.currentTarget.value)}
            onKeyDown={(e) => { if (e.key === "Enter") void handleCreate(); }}
            data-autofocus
          />
          <Text size="xs" c="dimmed">
            Created inside{" "}
            <strong>{data?.folder?.name ?? "All templates"}</strong>.
          </Text>
          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setNewOpened(false)}>Cancel</Button>
            <Button onClick={handleCreate} loading={saving}>Create</Button>
          </Group>
        </Stack>
      </Modal>

      <Modal opened={!!renameTarget} onClose={() => setRenameTarget(null)} title="Rename folder" centered size="sm">
        <Stack>
          <TextInput
            label="Folder name"
            value={renameValue}
            onChange={(e) => setRenameValue(e.currentTarget.value)}
            onKeyDown={(e) => { if (e.key === "Enter") void handleRename(); }}
            data-autofocus
          />
          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setRenameTarget(null)}>Cancel</Button>
            <Button onClick={handleRename} loading={saving}>Save</Button>
          </Group>
        </Stack>
      </Modal>

      <Modal opened={!!moveTarget} onClose={() => setMoveTarget(null)} title="Move folder" centered size="sm">
        <Stack>
          <Select
            label="New parent"
            placeholder="Top level"
            data={folderSelectOptions(allFolders).filter((o) => o.value !== moveTarget?.id)}
            value={moveParent}
            onChange={setMoveParent}
            clearable
            searchable
          />
          <Text size="xs" c="dimmed">
            A folder cannot be moved inside itself or its own subfolders.
          </Text>
          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setMoveTarget(null)}>Cancel</Button>
            <Button onClick={handleMove} loading={saving}>Move</Button>
          </Group>
        </Stack>
      </Modal>

      <Modal
        opened={!!deleteTarget}
        onClose={() => setDeleteTarget(null)}
        title={`Delete "${deleteTarget?.name}"`}
        centered
        size="md"
      >
        <Stack>
          <Text size="sm">What should happen to everything inside it?</Text>

          <Radio.Group value={deleteMode} onChange={(v) => setDeleteMode(v as typeof deleteMode)}>
            <Stack gap="sm">
              <Radio
                value="orphan"
                label="Move contents to the top level"
                description="Templates and subfolders survive and reappear at the root. Nothing is lost."
              />
              <Radio
                value="move"
                label="Move contents into another folder"
                description="Subfolders keep their own structure; only direct children are re-filed."
              />
              <Radio
                value="cascade"
                label="Delete the contents too"
                description="Deletes every template AND every image in this folder and all its subfolders. Cannot be undone."
              />
            </Stack>
          </Radio.Group>

          {deleteMode === "move" && (
            <Select
              label="Move into"
              placeholder="Top level"
              data={folderSelectOptions(allFolders).filter((o) => o.value !== deleteTarget?.id)}
              value={deleteMoveTo}
              onChange={setDeleteMoveTo}
              searchable
              clearable
            />
          )}

          {deleteMode === "cascade" && (
            <Alert color="red" icon={<IconAlertTriangle size={16} />} title="This deletes templates">
              <Text size="sm" mb="xs">
                Template IDs are referenced from promo codes, curated events and
                member offers. Deleting a template that live mail still points at
                will break that mail — the reference is a plain string, so nothing
                warns you.
              </Text>
              <Text size="sm" mb="xs">
                Images under this folder are deleted too. Any email already
                delivered that embeds one will show a broken image from then on,
                and that mail cannot be recalled or repaired.
              </Text>
              <TextInput
                label={`Type DELETE to confirm`}
                placeholder="DELETE"
                value={cascadeConfirm}
                onChange={(e) => setCascadeConfirm(e.currentTarget.value)}
              />
            </Alert>
          )}

          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setDeleteTarget(null)}>Cancel</Button>
            <Button
              color="red"
              loading={saving}
              disabled={
                (deleteMode === "move" && !deleteMoveTo) ||
                (deleteMode === "cascade" && cascadeConfirm !== "DELETE")
              }
              onClick={handleDelete}
            >
              {deleteMode === "cascade" ? "Delete folder and contents" : "Delete folder"}
            </Button>
          </Group>
        </Stack>
      </Modal>

      {/* wipe the folder's images/ subfolder */}
      <Modal
        opened={imagesWipeOpen}
        onClose={() => setImagesWipeOpen(false)}
        title="Delete this folder's images"
        centered
        size="sm"
      >
        <Stack>
          <Text size="sm">
            Delete every image stored under <strong>{data?.folder?.name}</strong>?
          </Text>
          <Alert color="red" icon={<IconAlertTriangle size={16} />}>
            <Text size="sm">
              Templates in this folder that use them will show broken images, and
              so will any email already delivered that embeds one. Delivered mail
              cannot be recalled or repaired.
            </Text>
          </Alert>
          <Text size="xs" c="dimmed">
            The templates themselves are not deleted.
          </Text>
          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setImagesWipeOpen(false)}>Cancel</Button>
            <Button color="red" loading={saving} onClick={handleImagesWipe}>
              Delete images
            </Button>
          </Group>
        </Stack>
      </Modal>

      {/* image delete */}
      <Modal
        opened={!!assetDeleteTarget}
        onClose={() => setAssetDeleteTarget(null)}
        title="Delete image"
        centered
        size="sm"
      >
        <Stack>
          <Text size="xs" ff="monospace" style={{ wordBreak: "break-all" }}>
            {assetDeleteTarget?.path}
          </Text>
          {assetDeleteTarget && assetDeleteTarget.usedBy.length > 0 ? (
            <Alert color="red" icon={<IconAlertTriangle size={16} />} title="Still in use">
              <Text size="sm">
                Referenced by {assetDeleteTarget.usedBy.join(", ")}. Those
                templates will show a broken image.
              </Text>
            </Alert>
          ) : (
            <Alert color="orange" icon={<IconAlertTriangle size={16} />}>
              <Text size="sm">
                No template lists this image — but a template edited by hand can
                reference a URL without it being recorded, so that is a lower
                bound, not a guarantee.
              </Text>
            </Alert>
          )}
          <Text size="xs" c="dimmed">
            Any email already delivered that embeds this URL will show a broken
            image from now on. That mail cannot be recalled or repaired.
          </Text>
          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setAssetDeleteTarget(null)}>Cancel</Button>
            <Button color="red" loading={saving} onClick={handleAssetDelete}>Delete image</Button>
          </Group>
        </Stack>
      </Modal>

      {/* template delete */}
      <Modal
        opened={!!templateDeleteTarget}
        onClose={() => setTemplateDeleteTarget(null)}
        title="Delete template"
        centered
        size="sm"
      >
        <Stack>
          <Text size="sm">
            Delete <strong>{templateDeleteTarget?.name}</strong>?
          </Text>
          <Alert color="red" icon={<IconAlertTriangle size={16} />}>
            <Text size="sm">
              If a promo code, curated event or member offer references this
              template ID, that mail stops working. The reference is a plain
              string, so nothing here can check it for you.
            </Text>
          </Alert>
          <Text size="xs" c="dimmed">
            Images stay — mail already delivered still loads them.
          </Text>
          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setTemplateDeleteTarget(null)}>Cancel</Button>
            <Button color="red" loading={saving} onClick={handleTemplateDelete}>Delete</Button>
          </Group>
        </Stack>
      </Modal>

      {/* Approval drawer — folders and templates share it. */}
      {workflowTarget && (
        <WorkflowSidebar
          opened
          onClose={() => setWorkflowTarget(null)}
          entityType={workflowTarget.type}
          entityId={workflowTarget.id}
          /*
           * A folder's change requests can point at any template shown in it.
           * For a template, the only sensible target is itself.
           */
          crTargets={
            workflowTarget.type === "EDM_FOLDER"
              ? (data?.templates ?? []).map((t) => ({ value: t.id, label: t.name }))
              : (data?.templates ?? [])
                  .filter((t) => t.id === workflowTarget.id)
                  .map((t) => ({ value: t.id, label: t.name }))
          }
          defaultCrTarget={
            workflowTarget.type === "EDM_TEMPLATE" ? workflowTarget.id : null
          }
          onWorkflowUpdated={() => {
            // The drawer owns the transition, so the badges behind it are stale
            // until asked again.
            void (workflowTarget.type === "EDM_FOLDER"
              ? folderWorkflow.refresh()
              : templateWorkflow.refresh());
          }}
        />
      )}
      </Stack>
    </Group>
  );
}

function AssetCard({ asset, onDelete }: { asset: EDMFolderAsset; onDelete?: () => void }) {
  const filename = asset.path.split("/").pop() ?? asset.path;
  return (
    <Paper withBorder radius="md" p={6}>
      <Box
        style={{
          backgroundColor: "var(--mantine-color-gray-1)",
          borderRadius: 6,
          height: 80,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          overflow: "hidden",
        }}
      >
        <Image
          src={asset.url}
          alt={asset.original}
          fit="contain"
          h={80}
          fallbackSrc=""
          // A broken thumbnail here is meaningful — the same URL is what a mail
          // client will fetch, so the placeholder is a real signal, not noise.
          onError={(e) => {
            (e.currentTarget as HTMLImageElement).style.display = "none";
          }}
        />
        <IconPhoto size={24} opacity={0.25} style={{ position: "absolute" }} />
      </Box>
      <Tooltip label={`${asset.original} — used by ${asset.usedBy.join(", ")}`} multiline w={260}>
        <Text size="xs" truncate mt={4}>{filename}</Text>
      </Tooltip>
      <Group justify="space-between" gap={4} mt={2}>
        <Text size="xs" c={asset.usedBy.length ? "dimmed" : "orange"}>
          {asset.usedBy.length ? `${asset.usedBy.length} use${asset.usedBy.length === 1 ? "" : "s"}` : "unused"}
        </Text>
        <Group gap={0} wrap="nowrap">
          <CopyButton value={asset.url}>
            {({ copied, copy }) => (
              <Tooltip label={copied ? "Copied" : "Copy URL"}>
                <ActionIcon size="xs" variant="subtle" color={copied ? "teal" : "gray"} onClick={copy}>
                  {copied ? <IconCheck size={12} /> : <IconCopy size={12} />}
                </ActionIcon>
              </Tooltip>
            )}
          </CopyButton>
          {onDelete && (
            <Tooltip label="Delete image">
              <ActionIcon size="xs" variant="subtle" color="red" onClick={onDelete}>
                <IconTrash size={12} />
              </ActionIcon>
            </Tooltip>
          )}
        </Group>
      </Group>
    </Paper>
  );
}
