import { useAtomRefresh, useAtomSet, useAtomValue } from "@effect/atom-react";
import { Link } from "@tanstack/react-router";
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
import * as Exit from "effect/Exit";
import { toast } from "sonner";
import type { ArtifactId, ArtifactPreview as ArtifactPreviewValue } from "@executor-js/sdk/shared";

import { trackEvent } from "../api/analytics";
import { usePreloadArtifactRenderer } from "../api/artifact-renderer";
import {
  artifactsOptimisticAtom,
  removeArtifactOptimistic,
  renameArtifactOptimistic,
} from "../api/atoms";
import { artifactWriteKeys } from "../api/reactivity-keys";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "../components/alert-dialog";
import { Button } from "../components/button";
import { ArtifactPreview } from "../components/artifact-preview";
import { ErrorState } from "../components/error-state";
import { PageContainer, PageHeader } from "../components/page";
import { isAsyncResultLoading } from "../lib/async-result";
import { useExecutorDocumentTitle } from "../lib/document-title";
import { formatRelativeTime } from "../lib/relative-time";
import { RenameArtifactDialog } from "./artifact-rename-dialog";

/** The wire row `artifacts.list` returns — no `code`, so the list stays cheap.
 *  The preview is the exception: the gallery is what draws it. */
interface ArtifactSummary {
  readonly id: ArtifactId;
  readonly title: string;
  readonly description: string | null;
  readonly preview: ArtifactPreviewValue | null;
  readonly updatedAt: number;
}

const LoadingState = () => (
  <div className="flex items-center gap-2 py-8">
    <div className="size-1.5 animate-pulse rounded-full bg-muted-foreground/30" />
    <p className="text-sm text-muted-foreground">Loading artifacts…</p>
  </div>
);

/**
 * Artifacts are never created here — a model makes them by calling `create-artifact`
 * over MCP — so the empty state teaches that path rather than offering a button
 * that cannot exist.
 */
const EmptyState = () => (
  <div
    data-slot="artifact-empty"
    className="rounded-lg border border-dashed border-border px-6 py-16 text-center"
  >
    <p className="text-sm text-muted-foreground">
      No artifacts yet. Ask an agent to render a UI and it appears here, ready to reopen.
    </p>
  </div>
);

/**
 * One artifact, as a gallery card.
 *
 * The whole card is the link — the preview is the biggest, most obvious target,
 * and a title-only hit area would waste it. The row actions therefore cannot be
 * nested inside it (a button inside an anchor is invalid, and the click would
 * navigate through them), so the link is a full-bleed overlay and the actions
 * sit above it on the z-axis. That keeps one accessible name for the card and
 * one focus stop, with the actions as their own stops after it.
 */
function ArtifactCard(props: {
  readonly artifact: ArtifactSummary;
  readonly onRename: (title: string) => void;
  readonly onRemove: () => void;
}) {
  const { artifact } = props;
  return (
    <div
      data-slot="artifact-card"
      className="group/artifact-card relative isolate flex flex-col overflow-hidden rounded-lg border border-border bg-card transition-colors duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-within:border-input hover:border-input"
    >
      {/* 16:10 preview, on its own plane, separated by the same hairline the
          rest of the console uses. */}
      <div className="relative aspect-[16/10] overflow-hidden border-b border-border bg-muted/40">
        <ArtifactPreview artifactId={artifact.id} preview={artifact.preview} />
      </div>

      <div className="flex min-w-0 flex-col gap-1 px-4 py-3">
        <h3 className="truncate text-sm font-medium text-foreground">
          <Link
            to="/{-$orgSlug}/artifacts/$artifactId"
            params={{ artifactId: artifact.id }}
            aria-label={`Open artifact ${artifact.title}`}
            className="outline-none after:absolute after:inset-0 after:content-[''] focus-visible:underline"
            onClick={() => trackEvent("artifact_opened", { surface: "list" })}
          >
            {artifact.title}
          </Link>
        </h3>
        {artifact.description ? (
          <p className="line-clamp-2 text-xs leading-relaxed text-muted-foreground">
            {artifact.description}
          </p>
        ) : null}
        <span className="mt-1 font-mono text-[11px] text-muted-foreground">
          {formatRelativeTime(artifact.updatedAt)}
        </span>
      </div>

      {/* Above the link overlay, so these stay clickable. Revealed on hover or
          focus, and pinned open while their own dialog is. */}
      <div className="absolute right-2 top-2 z-10 flex items-center gap-1 opacity-0 transition-opacity duration-150 focus-within:opacity-100 group-hover/artifact-card:opacity-100 has-[[data-state=open]]:opacity-100">
        <RenameArtifactDialog
          currentTitle={artifact.title}
          onRename={props.onRename}
          trigger={
            <Button
              type="button"
              variant="secondary"
              size="xs"
              className="bg-card text-muted-foreground hover:text-foreground"
            >
              Rename
            </Button>
          }
        />
        <AlertDialog>
          <AlertDialogTrigger asChild>
            <Button
              type="button"
              variant="secondary"
              size="xs"
              className="bg-card text-muted-foreground hover:text-destructive"
            >
              Delete
            </Button>
          </AlertDialogTrigger>
          <AlertDialogContent size="sm">
            <AlertDialogHeader>
              <AlertDialogTitle>Delete {artifact.title}?</AlertDialogTitle>
              <AlertDialogDescription>
                This removes the artifact for good. Agents will no longer find it by name.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel>Cancel</AlertDialogCancel>
              <AlertDialogAction variant="destructive" onClick={props.onRemove}>
                Delete Artifact
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>
      </div>
    </div>
  );
}

export function ArtifactsPage() {
  useExecutorDocumentTitle("Artifacts");
  // Opening a card is the only thing this page is for, and the renderer chunk is
  // the slowest part of doing so. Fetching it while the user is still choosing
  // means the detail page never has to wait for it — the renderer's own
  // placeholder simply stops existing for anyone arriving from here.
  usePreloadArtifactRenderer();
  const artifacts = useAtomValue(artifactsOptimisticAtom);
  const refreshArtifacts = useAtomRefresh(artifactsOptimisticAtom);
  const doRename = useAtomSet(renameArtifactOptimistic, { mode: "promiseExit" });
  const doRemove = useAtomSet(removeArtifactOptimistic, { mode: "promiseExit" });

  const handleRename = async (artifactId: ArtifactId, title: string) => {
    const exit = await doRename({
      params: { artifactId },
      payload: { title },
      reactivityKeys: artifactWriteKeys,
    });
    trackEvent("artifact_renamed", { success: Exit.isSuccess(exit) });
    if (Exit.isFailure(exit)) toast.error("Couldn't rename the artifact. Try again.");
  };

  const handleRemove = async (artifactId: ArtifactId) => {
    const exit = await doRemove({
      params: { artifactId },
      reactivityKeys: artifactWriteKeys,
    });
    trackEvent("artifact_removed", { success: Exit.isSuccess(exit) });
    if (Exit.isFailure(exit)) toast.error("Couldn't delete the artifact. Try again.");
  };

  return (
    // Wider than the settings column: three preview cards need the room, and
    // this page is a gallery rather than a form.
    <PageContainer className="max-w-6xl">
      <PageHeader
        title="Artifacts"
        description="Interactive components your agents generated. Ask an agent to render a UI and it is saved here — reopen it any time, or have the agent bring it back by name."
      />

      {isAsyncResultLoading(artifacts) ? (
        <LoadingState />
      ) : (
        AsyncResult.match(artifacts, {
          onInitial: () => <LoadingState />,
          onFailure: () => (
            <ErrorState message="Failed to load artifacts" onRetry={refreshArtifacts} />
          ),
          onSuccess: ({ value }) => {
            // Most-recently-updated first: an artifact just generated by an
            // agent is the one the user is most likely coming here to open.
            const rows = [...value].sort((a, b) => b.updatedAt - a.updatedAt);
            return (
              <section>
                <h2 className="mb-3 flex items-center font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground">
                  Saved artifacts
                  {rows.length > 0 ? (
                    <span className="ml-2 font-normal tabular-nums">{rows.length}</span>
                  ) : null}
                </h2>
                {rows.length === 0 ? (
                  <EmptyState />
                ) : (
                  <div
                    data-slot="artifact-grid"
                    className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"
                  >
                    {rows.map((artifact) => (
                      <ArtifactCard
                        key={artifact.id}
                        artifact={artifact}
                        onRename={(title) => void handleRename(artifact.id, title)}
                        onRemove={() => void handleRemove(artifact.id)}
                      />
                    ))}
                  </div>
                )}
              </section>
            );
          },
        })
      )}
    </PageContainer>
  );
}

export type { ArtifactSummary };
