<script lang="ts">
  import Icon from "@iconify/svelte";
  import * as api from "$lib/ts/api";
  import type { BrowseEntry, EntryKind } from "$lib/ts/api";
  import { clampMenu } from "$lib/ts/menu-position";
  import {
    app,
    breadcrumbs,
    browseTo,
    cloudBreadcrumbs,
    linkedDocument,
    linkedProject,
    openCloudFolder,
    openTarget,
    refreshCloud,
    refreshEntries,
    setError,
  } from "$lib/ts/state.svelte";

  interface Props {
    onnewfolder: () => void;
    onnewproject: () => void;
    onnewdocument: () => void;
    onupload: () => void;
    onrename: (entry: BrowseEntry) => void;
    ondelete: (entry: BrowseEntry) => void;
    onlink: (entry: BrowseEntry) => void;
    onsavetocloud: (entry: BrowseEntry) => void;
    onviewimage: (paths: string[], index: number) => void;
    ondownloaddocument: (documentId: string, title: string) => void;
    onremovedownload: (path: string) => void;
    ondownloadfile: (fileId: string, name: string) => void;
    ondeletefile: (fileId: string) => void;
    oncloneproject: (cloudProjectId: string, name: string) => void;
    ondeleteproject: (cloudProjectId: string) => void;
    ondeletedocument: (documentId: string) => void;
    onnewcloudproject: () => void;
    onnewclouddocument: () => void;
    onsignin: () => void;
  }

  let {
    onnewfolder,
    onnewproject,
    onnewdocument,
    onupload,
    onrename,
    ondelete,
    onlink,
    onsavetocloud,
    onviewimage,
    ondownloaddocument,
    onremovedownload,
    ondownloadfile,
    ondeletefile,
    oncloneproject,
    ondeleteproject,
    ondeletedocument,
    onnewcloudproject,
    onnewclouddocument,
    onsignin,
  }: Props = $props();

  let menuFor = $state<string | null>(null);
  let menuAt = $state({ x: 0, y: 0 });

  function openContextMenu(event: MouseEvent, path: string) {
    event.preventDefault();
    event.stopPropagation();
    menuAt = { x: event.clientX, y: event.clientY };
    menuFor = path;
  }

  let cloudMenuFor = $state<string | null>(null);
  let cloudMenuAt = $state({ x: 0, y: 0 });

  function openCloudContextMenu(event: MouseEvent, id: string) {
    event.preventDefault();
    event.stopPropagation();
    cloudMenuAt = { x: event.clientX, y: event.clientY };
    cloudMenuFor = id;
  }

  const trail = $derived(breadcrumbs());
  const cloudTrail = $derived(cloudBreadcrumbs());

  $effect(() => {
    if (app.scope === "cloud" && app.account) {
      refreshCloud();
    }
  });

  const containers = $derived(
    app.entries.filter(
      (entry) => entry.kind === "folder" || entry.kind === "project",
    ),
  );

  const documents = $derived(
    app.entries.filter(
      (entry) => entry.kind === "document" || entry.kind === "file",
    ),
  );

  let thumbs = $state<Record<string, { kind: string; data: string }>>({});
  let cloudThumbs = $state<Record<string, { kind: string; data: string }>>({});

  $effect(() => {
    if (app.scope !== "cloud") return;

    const pending = [
      ...app.linkedDocuments.map((linked) => linked.path),
      ...app.linkedProjects.map((linked) => linked.path),
    ];
    let cancelled = false;

    (async () => {
      for (const path of pending) {
        if (cancelled) return;
        if (cloudThumbs[path]) continue;
        try {
          const result = await api.thumbnail(path);
          if (!cancelled) cloudThumbs[path] = result;
        } catch {
          continue;
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  });

  $effect(() => {
    const pending = documents.map((entry) => entry.path);
    let cancelled = false;

    (async () => {
      for (const path of pending) {
        if (cancelled) return;
        if (thumbs[path]) continue;
        try {
          const result = await api.thumbnail(path);
          if (!cancelled) thumbs[path] = result;
        } catch {
          continue;
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  });

  const iconFor: Record<EntryKind, string> = {
    project: "ph:folder-star",
    folder: "ph:folder",
    document: "ph:file-text",
    file: "ph:file",
  };

  const colorFor: Record<EntryKind, string> = {
    project: "text-[var(--color-accent)]",
    folder: "text-[var(--color-ink-muted)]",
    document: "text-[var(--color-accent)]",
    file: "text-[var(--color-ink-muted)]",
  };

  function relativeTime(value: string | null): string {
    if (!value) return "";
    const then = new Date(value).getTime();
    if (Number.isNaN(then)) return "";

    const minutes = Math.round((Date.now() - then) / 60000);
    if (minutes < 1) return "just now";
    if (minutes < 60) return `${minutes}m ago`;
    if (minutes < 1440) return `${Math.round(minutes / 60)}h ago`;
    return `${Math.round(minutes / 1440)}d ago`;
  }

  function syncLabel(entry: BrowseEntry): string {
    if (entry.sync_state === "pending") return "Local changes not yet synced";
    if (entry.sync_state === "synced") {
      const when = relativeTime(entry.last_synced_at);
      return when ? `Synced ${when}` : "Synced";
    }
    return "Linked to the cloud";
  }

  const imagePaths = $derived(
    app.entries
      .filter((entry) => api.isImagePath(entry.path))
      .map((entry) => entry.path),
  );

  let dragging = $state<string | null>(null);
  let dropTarget = $state<string | null>(null);

  async function moveTo(source: string, destination: string) {
    if (source === destination) return;
    const parent = source.includes("/") ? source.slice(0, source.lastIndexOf("/")) : "";
    if (parent === destination) return;
    try {
      await api.moveEntry(source, destination);
      await refreshEntries();
    } catch (error) {
      setError(error);
    }
  }

  function startDrag(event: DragEvent, path: string) {
    dragging = path;
    event.dataTransfer?.setData("text/plain", path);
  }

  function endDrag() {
    dragging = null;
    dropTarget = null;
  }

  function allowDrop(event: DragEvent, path: string) {
    if (!dragging) return;
    event.preventDefault();
    dropTarget = path;
  }

  function handleDrop(event: DragEvent, destination: string) {
    event.preventDefault();
    const source = dragging ?? event.dataTransfer?.getData("text/plain");
    dragging = null;
    dropTarget = null;
    if (source) moveTo(source, destination);
  }

  function activate(entry: BrowseEntry) {
    if (entry.kind === "folder") {
      browseTo(entry.path);
    } else if (entry.kind === "project" || entry.kind === "document") {
      openTarget(entry.path);
    } else if (api.isImagePath(entry.path)) {
      onviewimage(imagePaths, imagePaths.indexOf(entry.path));
    }
  }

  function formatSize(bytes: number): string {
    if (bytes < 1024) return `${bytes} B`;
    if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
    return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
  }

  function formatDate(value: string | null): string {
    if (!value) return "";
    const date = new Date(value);
    return Number.isNaN(date.getTime()) ? "" : date.toLocaleDateString();
  }
</script>

{#snippet cloudMenu(
  id: string,
  onopen: (() => void) | null,
  ondownload: () => void,
  onremove: (() => void) | null,
  ondelete: (() => void) | null,
)}
  {#if cloudMenuFor === id}
    <div
      use:clampMenu
      class="fixed z-50 flex w-44 flex-col rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] py-1 text-xs shadow-lg"
      style="left: {cloudMenuAt.x}px; top: {cloudMenuAt.y}px"
    >
      {#if onopen}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            onopen();
            cloudMenuFor = null;
          }}
        >
          Open
        </button>
      {:else}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            ondownload();
            cloudMenuFor = null;
          }}
        >
          Download
        </button>
      {/if}
      {#if onremove}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            onremove();
            cloudMenuFor = null;
          }}
        >
          Remove from this device
        </button>
      {/if}
      {#if ondelete}
        <button
          class="px-3 py-1.5 text-left text-[var(--color-danger)] hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            ondelete();
            cloudMenuFor = null;
          }}
        >
          Delete from cloud
        </button>
      {/if}
    </div>
  {/if}
{/snippet}

{#snippet cloudCard(
  id: string,
  icon: string,
  title: string,
  meta: string,
  link: { sync_state: string | null; path: string } | undefined,
  onopen: (() => void) | null,
  ondownload: () => void,
  onremove: (() => void) | null,
  ondelete: (() => void) | null,
)}
  <div
    class="group flex flex-col overflow-hidden rounded-lg border border-[var(--color-line)] bg-[var(--color-surface)] transition hover:border-[var(--color-accent)] hover:shadow-sm"
    oncontextmenu={(event) => openCloudContextMenu(event, id)}
  >
    {@render cloudMenu(id, onopen, ondownload, onremove, ondelete)}
    <div
      class="flex h-24 items-center justify-center overflow-hidden border-b border-[var(--color-line)] bg-[var(--color-surface-muted)]"
    >
      {#if link && cloudThumbs[link.path]}
        {#if cloudThumbs[link.path].kind === "svg"}
          <span
            class="flex w-full items-start justify-center bg-white p-1 [&_svg]:h-auto [&_svg]:w-full"
          >
            {@html cloudThumbs[link.path].data}
          </span>
        {:else}
          <img
            src={cloudThumbs[link.path].data}
            alt={title}
            class="h-full w-full object-contain"
          />
        {/if}
      {:else}
        <Icon
          {icon}
          class="text-3xl {link
            ? 'text-[var(--color-accent)]'
            : 'text-[var(--color-ink-muted)]'}"
        />
      {/if}
    </div>

    <div class="flex flex-col gap-2.5 p-3">
    <div class="flex items-start gap-2">
      <div class="min-w-0 flex-1">
        <p class="truncate text-xs font-medium" {title}>{title}</p>
        <p class="truncate text-[10px] text-[var(--color-ink-muted)]">{meta}</p>
      </div>

      {#if link}
        <span
          class="flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[9px] font-medium
            {link.sync_state === 'pending'
            ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]'
            : 'bg-[var(--color-success)]/10 text-[var(--color-success)]'}"
          title={link.sync_state === "pending"
            ? "Local changes not yet synced"
            : "On this device and up to date"}
        >
          <Icon
            icon={link.sync_state === "pending"
              ? "ph:cloud-arrow-up"
              : "ph:cloud-check"}
            class="text-[11px]"
          />
          {link.sync_state === "pending" ? "Unsynced" : "Synced"}
        </span>
      {/if}
    </div>

    <div class="flex gap-1">
      {#if link && onopen}
        <button
          class="flex flex-1 items-center justify-center gap-1 rounded-md bg-[var(--color-accent)] px-2 py-1.5 text-[10px] font-medium text-white transition hover:opacity-90"
          onclick={onopen}
        >
          <Icon icon="ph:pencil-simple" />
          Open
        </button>
      {:else}
        <button
          class="flex flex-1 items-center justify-center gap-1 rounded-md border border-[var(--color-line)] px-2 py-1.5 text-[10px] transition hover:bg-[var(--color-surface-muted)]"
          onclick={ondownload}
        >
          <Icon icon="ph:download-simple" />
          Download
        </button>
      {/if}

      {#if onremove || ondelete}
        <button
          class="rounded-md border border-[var(--color-line)] px-2 py-1.5 text-[10px] text-[var(--color-ink-muted)] transition hover:border-[var(--color-danger)] hover:text-[var(--color-danger)]"
          onclick={onremove ?? ondelete}
          aria-label={onremove ? "Remove" : "Delete"}
        >
          <Icon icon="ph:trash" />
        </button>
      {/if}
    </div>
    </div>
  </div>
{/snippet}

{#snippet syncBadge(entry: BrowseEntry)}
  {#if entry.cloud_linked}
    <span class="flex shrink-0 items-center" title={syncLabel(entry)}>
      {#if entry.sync_state === "pending"}
        <Icon
          icon="ph:cloud-arrow-up"
          class="text-sm text-[var(--color-accent)]"
        />
      {:else}
        <Icon
          icon="ph:cloud-check"
          class="text-sm text-[var(--color-success)]"
        />
      {/if}
    </span>
  {/if}
{/snippet}

{#snippet actions(entry: BrowseEntry)}
  <button
    data-card-menu
    class="shrink-0 rounded p-1 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-sunken)] hover:text-[var(--color-ink)]"
    onclick={(event) => {
      event.stopPropagation();
      if (menuFor === entry.path) {
        menuFor = null;
        return;
      }
      const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
      menuAt = { x: rect.right, y: rect.bottom + 4 };
      menuFor = entry.path;
    }}
    aria-label="Actions"
  >
    <Icon icon="ph:dots-three-vertical" />
  </button>

  {#if menuFor === entry.path}
    <div
      use:clampMenu
      class="fixed z-50 flex w-40 flex-col rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] py-1 text-xs shadow-lg"
      style="left: {menuAt.x}px; top: {menuAt.y}px"
    >
      {#if entry.kind === "project" || entry.kind === "document"}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            openTarget(entry.path);
            menuFor = null;
          }}
        >
          Open in editor
        </button>
      {:else if api.isImagePath(entry.path)}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            onviewimage(imagePaths, imagePaths.indexOf(entry.path));
            menuFor = null;
          }}
        >
          Open image
        </button>
      {/if}
      {#if entry.kind === "project" && !entry.cloud_project_id && app.account}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            onlink(entry);
            menuFor = null;
          }}
        >
          Upload to cloud
        </button>
      {/if}
      {#if entry.kind === "document" && !entry.cloud_linked && app.account}
        <button
          class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
          onclick={() => {
            onsavetocloud(entry);
            menuFor = null;
          }}
        >
          Save to cloud
        </button>
      {/if}
      <button
        class="px-3 py-1.5 text-left hover:bg-[var(--color-surface-sunken)]"
        onclick={() => {
          onrename(entry);
          menuFor = null;
        }}
      >
        Rename
      </button>
      <button
        class="px-3 py-1.5 text-left text-[var(--color-danger)] hover:bg-[var(--color-surface-sunken)]"
        onclick={() => {
          ondelete(entry);
          menuFor = null;
        }}
      >
        Delete
      </button>
    </div>
  {/if}
{/snippet}

<svelte:window
  on:click={(event) => {
    if (!(event.target as HTMLElement).closest("[data-card-menu]")) {
      menuFor = null;
    }
    cloudMenuFor = null;
  }}
/>

<div
  class="flex h-full flex-col bg-[var(--color-surface-muted)]"
  oncontextmenu={(event) => event.preventDefault()}
>
  <div
    class="flex items-center gap-2 border-b border-[var(--color-line)] bg-[var(--color-surface)] px-4 py-2.5"
  >
    <div class="flex rounded-lg bg-[var(--color-surface-sunken)] p-0.5 text-xs font-medium">
      {#each [["local", "Local", "ph:hard-drives"], ["cloud", "Cloud", "ph:cloud"]] as [value, label, icon]}
        <button
          class="flex items-center gap-1.5 rounded-md px-3 py-1.5 transition
            {app.scope === value
            ? 'bg-[var(--color-surface)] text-[var(--color-ink)] shadow-sm'
            : 'text-[var(--color-ink-muted)] hover:text-[var(--color-ink)]'}"
          onclick={() => (app.scope = value as "local" | "cloud")}
        >
          <Icon {icon} />
          {label}
        </button>
      {/each}
    </div>

    <div class="flex-1"></div>

    {#if app.scope === "local"}
      <button
        class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
        onclick={onupload}
      >
        <Icon icon="ph:upload-simple" />
        Import
      </button>
      <button
        class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
        onclick={onnewfolder}
      >
        <Icon icon="ph:folder-plus" />
        Folder
      </button>
      <button
        class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
        onclick={onnewproject}
      >
        <Icon icon="ph:folder-star" />
        Project
      </button>
      <button
        class="flex items-center gap-1.5 rounded-md bg-[var(--color-accent)] px-2.5 py-1.5 text-xs font-medium text-white transition hover:opacity-90"
        onclick={onnewdocument}
      >
        <Icon icon="ph:plus" />
        New document
      </button>
    {:else if app.account}
      <button
        class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
        onclick={onnewcloudproject}
      >
        <Icon icon="ph:folder-star" />
        Project
      </button>
      <button
        class="flex items-center gap-1.5 rounded-md bg-[var(--color-accent)] px-2.5 py-1.5 text-xs font-medium text-white transition hover:opacity-90"
        onclick={onnewclouddocument}
      >
        <Icon icon="ph:plus" />
        New document
      </button>
    {/if}
  </div>

  {#if app.scope === "local"}
    <div
      class="flex items-center gap-1 border-b border-[var(--color-line)] bg-[var(--color-surface)] px-4 py-2 text-xs"
    >
      <button
        class="flex items-center gap-1.5 rounded px-2 py-1 transition hover:bg-[var(--color-surface-muted)]
          {app.currentDir === '' ? 'font-medium' : 'text-[var(--color-ink-muted)]'}
          {dropTarget === '' ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]' : ''}"
        onclick={() => browseTo("")}
        ondragover={(event) => allowDrop(event, "")}
        ondragleave={() => {
          if (dropTarget === "") dropTarget = null;
        }}
        ondrop={(event) => handleDrop(event, "")}
      >
        <Icon icon="ph:house" />
        Workspace
      </button>

      {#each trail as crumb, index}
        <Icon icon="ph:caret-right" class="text-[10px] text-[var(--color-ink-muted)]" />
        <button
          class="rounded px-2 py-1 transition hover:bg-[var(--color-surface-muted)]
            {index === trail.length - 1 ? 'font-medium' : 'text-[var(--color-ink-muted)]'}
            {dropTarget === crumb.path ? 'bg-[var(--color-accent-soft)] text-[var(--color-accent)]' : ''}"
          onclick={() => browseTo(crumb.path)}
          ondragover={(event) => allowDrop(event, crumb.path)}
          ondragleave={() => {
            if (dropTarget === crumb.path) dropTarget = null;
          }}
          ondrop={(event) => handleDrop(event, crumb.path)}
        >
          {crumb.name}
        </button>
      {/each}
    </div>

    <div class="scroll-thin flex-1 overflow-y-auto p-4">
      {#if app.entries.length === 0}
        <div
          class="flex h-full flex-col items-center justify-center gap-3 text-[var(--color-ink-muted)]"
        >
          <Icon icon="ph:folder-open" class="text-5xl" />
          <p class="text-sm">This folder is empty.</p>
          <div class="flex gap-2">
            <button
              class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90"
              onclick={onnewdocument}
            >
              New document
            </button>
            <button
              class="rounded-md border border-[var(--color-line)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface)]"
              onclick={onnewproject}
            >
              New project
            </button>
          </div>
        </div>
      {:else}
        <div class="flex flex-col gap-5">
          {#if containers.length > 0}
            <section class="flex flex-col gap-2">
              <h2
                class="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-ink-muted)]"
              >
                Folders
              </h2>
              <div class="grid grid-cols-[repeat(auto-fill,minmax(210px,1fr))] gap-2">
                {#each containers as entry (entry.path)}
                  <div
                    class="group relative flex items-center gap-2.5 rounded-lg border px-3 py-2.5 transition hover:border-[var(--color-accent)] hover:bg-[var(--color-surface)]
                      {dropTarget === entry.path
                      ? 'border-[var(--color-accent)] bg-[var(--color-accent-soft)]'
                      : 'border-[var(--color-line)] bg-[var(--color-surface-sunken)]'}"
                    draggable="true"
                    ondragstart={(event) => startDrag(event, entry.path)}
                    ondragend={endDrag}
                    ondragover={(event) => allowDrop(event, entry.path)}
                    ondragleave={() => {
                      if (dropTarget === entry.path) dropTarget = null;
                    }}
                    ondrop={(event) => handleDrop(event, entry.path)}
                    oncontextmenu={(event) => openContextMenu(event, entry.path)}
                  >
                    <button
                      class="flex min-w-0 flex-1 items-center gap-2.5 text-left"
                      ondblclick={() => activate(entry)}
                      onclick={() => activate(entry)}
                    >
                      <Icon
                        icon={entry.kind === "project"
                          ? "ph:folder-star-fill"
                          : "ph:folder-fill"}
                        class="shrink-0 text-2xl {entry.kind === 'project'
                          ? 'text-[var(--color-accent)]'
                          : 'text-[var(--color-ink-muted)]'}"
                      />
                      <span class="min-w-0 flex-1">
                        <span class="flex items-center gap-1.5">
                          <span
                            class="truncate text-xs font-medium"
                            title={entry.name}
                          >
                            {entry.name}
                          </span>
                          {@render syncBadge(entry)}
                        </span>
                        <span
                          class="block truncate text-[10px] text-[var(--color-ink-muted)]"
                        >
                          {entry.kind === "project" ? "Project · " : ""}{entry.child_count}
                          items
                        </span>
                      </span>
                    </button>

                    {@render actions(entry)}
                  </div>
                {/each}
              </div>
            </section>
          {/if}

          {#if documents.length > 0}
            <section class="flex flex-col gap-2">
              <h2
                class="text-[10px] font-semibold uppercase tracking-wider text-[var(--color-ink-muted)]"
              >
                Documents
              </h2>
              <div class="grid grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-3">
                {#each documents as entry (entry.path)}
                  <div
                    class="group relative flex flex-col overflow-hidden rounded-lg border border-[var(--color-line)] bg-[var(--color-surface)] transition hover:border-[var(--color-accent)] hover:shadow-md"
                    draggable="true"
                    ondragstart={(event) => startDrag(event, entry.path)}
                    ondragend={endDrag}
                    oncontextmenu={(event) => openContextMenu(event, entry.path)}
                  >
                    <button
                      class="flex flex-col text-left"
                      ondblclick={() => activate(entry)}
                      onclick={() => activate(entry)}
                    >
                      <span
                        class="flex h-24 items-center justify-center overflow-hidden border-b border-[var(--color-line)] bg-[var(--color-surface-muted)]"
                      >
                        {#if thumbs[entry.path]?.kind === "svg"}
                          <span
                            class="flex w-full origin-top scale-100 items-start justify-center bg-white p-1 [&_svg]:h-auto [&_svg]:w-full"
                          >
                            {@html thumbs[entry.path].data}
                          </span>
                        {:else if thumbs[entry.path]?.kind === "image"}
                          <img
                            src={thumbs[entry.path].data}
                            alt={entry.name}
                            class="h-full w-full object-contain"
                          />
                        {:else}
                          <Icon
                            icon={iconFor[entry.kind]}
                            class="text-3xl {colorFor[entry.kind]}"
                          />
                        {/if}
                      </span>

                    </button>

                    <div class="flex items-center gap-1 px-2.5 py-2">
                      <button
                        class="flex min-w-0 flex-1 flex-col gap-0.5 text-left"
                        onclick={() => activate(entry)}
                      >
                        <span class="flex items-center gap-1.5">
                          <span
                            class="min-w-0 flex-1 truncate text-xs font-medium"
                            title={entry.name}
                          >
                            {entry.name}
                          </span>
                          {@render syncBadge(entry)}
                        </span>
                        <span class="text-[10px] text-[var(--color-ink-muted)]">
                          {#if entry.cloud_linked && entry.sync_state === "pending"}
                            Not synced
                          {:else if entry.cloud_linked}
                            Synced {relativeTime(entry.last_synced_at)}
                          {:else}
                            {formatSize(entry.size)}
                            {#if entry.modified}
                              · {formatDate(entry.modified)}
                            {/if}
                          {/if}
                        </span>
                      </button>

                      {@render actions(entry)}
                    </div>
                  </div>
                {/each}
              </div>
            </section>
          {/if}
        </div>
      {/if}
    </div>
  {:else}
    <div class="scroll-thin flex-1 overflow-y-auto p-4">
      {#if !app.account}
        <div
          class="flex h-full flex-col items-center justify-center gap-3 text-[var(--color-ink-muted)]"
        >
          <Icon icon="ph:cloud-slash" class="text-5xl" />
          <p class="max-w-xs text-center text-sm">
            Connect a TypstDrive account to sync your projects across devices.
          </p>
          <button
            class="rounded-md bg-[var(--color-accent)] px-3 py-2 text-xs font-medium text-white hover:opacity-90"
            onclick={onsignin}
          >
            Sign in
          </button>
        </div>
      {:else}
        <div class="mb-4 flex items-center gap-2">
          <button
            class="flex items-center gap-2 rounded-lg border px-3.5 py-2 text-xs font-medium transition
              {app.cloudFolder !== 'shared'
              ? 'border-[var(--color-accent)] bg-[var(--color-accent)] text-white shadow-sm'
              : 'border-[var(--color-line)] bg-[var(--color-surface)] text-[var(--color-ink-muted)] hover:border-[var(--color-accent)] hover:text-[var(--color-ink)]'}"
            onclick={() => openCloudFolder(null)}
          >
            <Icon icon="ph:cloud-fill" class="text-base" />
            My Drive
          </button>

          <button
            class="flex items-center gap-2 rounded-lg border px-3.5 py-2 text-xs font-medium transition
              {app.cloudFolder === 'shared'
              ? 'border-[var(--color-accent)] bg-[var(--color-accent)] text-white shadow-sm'
              : 'border-[var(--color-line)] bg-[var(--color-surface)] text-[var(--color-ink-muted)] hover:border-[var(--color-accent)] hover:text-[var(--color-ink)]'}"
            onclick={() => openCloudFolder("shared")}
          >
            <Icon icon="ph:users-three-fill" class="text-base" />
            Shared with me
          </button>

          {#if app.cloudLoading}
            <Icon
              icon="ph:circle-notch"
              class="animate-spin text-base text-[var(--color-accent)]"
            />
          {/if}
        </div>

        {#if cloudTrail.length > 0}
          <div class="mb-3 flex flex-wrap items-center gap-1 text-xs">
            <button
              class="rounded px-2 py-1 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface)] hover:text-[var(--color-ink)]"
              onclick={() => openCloudFolder(null)}
            >
              My Drive
            </button>

            {#each cloudTrail as folder, index}
              <Icon
                icon="ph:caret-right"
                class="text-[10px] text-[var(--color-ink-muted)]"
              />
              <button
                class="rounded px-2 py-1 transition hover:bg-[var(--color-surface)]
                  {index === cloudTrail.length - 1
                  ? 'font-medium'
                  : 'text-[var(--color-ink-muted)] hover:text-[var(--color-ink)]'}"
                onclick={() => openCloudFolder(folder.id)}
              >
                {folder.name}
              </button>
            {/each}
          </div>
        {/if}

        {#if app.cloudFolders.length > 0}
          <div
            class="mb-4 grid grid-cols-[repeat(auto-fill,minmax(210px,1fr))] gap-2"
          >
            {#each app.cloudFolders as folder (folder.id)}
              <div class="relative">
                <button
                  class="flex w-full items-center gap-2.5 rounded-lg border border-[var(--color-line)] bg-[var(--color-surface-sunken)] px-3 py-2.5 text-left transition hover:border-[var(--color-accent)] hover:bg-[var(--color-surface)]"
                  onclick={() => openCloudFolder(folder.id)}
                  oncontextmenu={(event) =>
                    openCloudContextMenu(event, `folder:${folder.id}`)}
                >
                  <Icon
                    icon="ph:folder-fill"
                    class="shrink-0 text-2xl text-[var(--color-ink-muted)]"
                  />
                  <span class="truncate text-xs font-medium">{folder.name}</span>
                </button>
                {@render cloudMenu(
                  `folder:${folder.id}`,
                  () => openCloudFolder(folder.id),
                  () => {},
                  null,
                  null,
                )}
              </div>
            {/each}
          </div>
        {/if}

        {#if app.cloudDocuments.length > 0}
          <h2
            class="mb-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-ink-muted)]"
          >
            Documents
          </h2>
          <div
            class="mb-4 grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-3"
          >
            {#each app.cloudDocuments as entry (entry.id)}
              {@const linked = linkedDocument(entry.id)}
              {@render cloudCard(
                `document:${entry.id}`,
                "ph:file-text",
                entry.title,
                linked
                  ? `Document · ${entry.role}`
                  : `${entry.role} · ${formatDate(entry.updated_at)}`,
                linked,
                linked ? () => openTarget(linked.path) : null,
                () => ondownloaddocument(entry.id, entry.title),
                linked ? () => onremovedownload(linked.path) : null,
                () => ondeletedocument(entry.id),
              )}
            {/each}
          </div>
        {/if}

        {#if app.cloudFiles.length > 0}
          <h2
            class="mb-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-ink-muted)]"
          >
            Images and fonts
          </h2>
          <div
            class="mb-4 grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-3"
          >
            {#each app.cloudFiles as file (file.id)}
              <div
                class="flex items-center gap-2.5 rounded-lg border border-[var(--color-line)] bg-[var(--color-surface)] p-3 transition hover:border-[var(--color-accent)]"
                oncontextmenu={(event) =>
                  openCloudContextMenu(event, `file:${file.id}`)}
              >
                {@render cloudMenu(
                  `file:${file.id}`,
                  null,
                  () => ondownloadfile(file.id, file.name),
                  null,
                  () => ondeletefile(file.id),
                )}
                <Icon
                  icon={api.isImagePath(file.name)
                    ? "ph:image"
                    : /\.(ttf|otf|ttc|otc)$/i.test(file.name)
                      ? "ph:text-aa"
                      : "ph:file"}
                  class="shrink-0 text-xl text-[var(--color-accent)]"
                />
                <div class="min-w-0 flex-1">
                  <p class="truncate text-xs font-medium" title={file.name}>
                    {file.name}
                  </p>
                  <p class="truncate text-[10px] text-[var(--color-ink-muted)]">
                    {formatDate(file.created_at)}
                  </p>
                </div>
                <button
                  class="shrink-0 rounded-md border border-[var(--color-line)] p-1.5 text-[var(--color-ink-muted)] transition hover:border-[var(--color-accent)] hover:text-[var(--color-accent)]"
                  onclick={() => ondownloadfile(file.id, file.name)}
                  title="Add to shared assets on this device"
                  aria-label="Download"
                >
                  <Icon icon="ph:download-simple" />
                </button>
              </div>
            {/each}
          </div>
        {/if}

        {#if app.cloudProjects.length === 0 && app.cloudDocuments.length === 0 && app.cloudFolders.length === 0 && app.cloudFiles.length === 0}
          <div
            class="flex flex-col items-center justify-center gap-3 py-16 text-[var(--color-ink-muted)]"
          >
            <Icon icon="ph:cloud" class="text-5xl" />
            <p class="text-sm">Nothing here yet.</p>
            {#if app.cloudFolder !== "shared"}
              <div class="flex gap-2">
                <button
                  class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90"
                  onclick={onnewclouddocument}
                >
                  New document
                </button>
                <button
                  class="rounded-md border border-[var(--color-line)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface)]"
                  onclick={onnewcloudproject}
                >
                  New project
                </button>
              </div>
            {/if}
          </div>
        {/if}

        {#if app.cloudProjects.length > 0}
          <h2
            class="mb-2 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-ink-muted)]"
          >
            Cloud projects
          </h2>
        {/if}
        <div class="grid grid-cols-[repeat(auto-fill,minmax(220px,1fr))] gap-3">
          {#each app.cloudProjects as project (project.id)}
            {@const linked = linkedProject(project.id)}
            {@render cloudCard(
              `project:${project.id}`,
              "ph:folder-star",
              project.name,
              linked
                ? `Project · ${project.role}`
                : `${project.role} · ${formatDate(project.updated_at)}`,
              linked,
              linked ? () => openTarget(linked.path) : null,
              () => oncloneproject(project.id, project.name),
              null,
              project.role === "owner" ? () => ondeleteproject(project.id) : null,
            )}
          {/each}
        </div>
      {/if}
    </div>
  {/if}
</div>
