import { BookOpenIcon, PlusIcon } from "@heroicons/react/20/solid";
import { NoSymbolIcon } from "@heroicons/react/24/solid";
import { Outlet, useParams, type MetaFunction } from "@remix-run/react";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { tryCatch } from "@trigger.dev/core";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { BulkActionsNone } from "~/components/BlankStatePanels";
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { PaginationControls } from "~/components/primitives/Pagination";
import { Paragraph } from "~/components/primitives/Paragraph";
import {
  RESIZABLE_PANEL_ANIMATION,
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
  collapsibleHandleClassName,
} from "~/components/primitives/Resizable";
import {
  Table,
  TableBlankRow,
  TableBody,
  TableCell,
  TableCellMenu,
  TableHeader,
  TableHeaderCell,
  TableRow,
} from "~/components/primitives/Table";
import { TruncatedCopyableValue } from "~/components/primitives/TruncatedCopyableValue";
import { AbortBulkActionDialog } from "~/components/runs/v3/AbortBulkActionDialog";
import { BulkActionStatusCombo, BulkActionTypeCombo } from "~/components/runs/v3/BulkAction";
import { UserAvatar } from "~/components/UserProfilePhoto";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { findProjectBySlug } from "~/models/project.server";
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
import {
  type BulkActionListItem,
  BulkActionListPresenter,
} from "~/presenters/v3/BulkActionListPresenter.server";
import { rbac } from "~/services/rbac.server";
import { checkPermissions } from "~/services/routeBuilders/permissions.server";
import { requireUserId } from "~/services/session.server";
import { cn } from "~/utils/cn";
import {
  docsPath,
  EnvironmentParamSchema,
  v3BulkActionPath,
  v3CreateBulkActionPath,
} from "~/utils/pathBuilder";

export const meta: MetaFunction = () => {
  return [
    {
      title: `Bulk actions | Trigger.dev`,
    },
  ];
};

const SearchParamsSchema = z.object({
  page: z.coerce.number().optional(),
});

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
  const userId = await requireUserId(request);

  const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params);

  const project = await findProjectBySlug(organizationSlug, projectParam, userId);
  if (!project) {
    throw new Response("Not Found", { status: 404 });
  }

  const environment = await findEnvironmentBySlug(project.id, envParam, userId);
  if (!environment) {
    throw new Response("Not Found", { status: 404 });
  }

  try {
    const url = new URL(request.url);
    const { page } = SearchParamsSchema.parse(Object.fromEntries(url.searchParams));

    const presenter = new BulkActionListPresenter();
    const [error, data] = await tryCatch(
      presenter.call({
        environmentId: environment.id,
        page,
      })
    );

    if (error) {
      throw new Error(error.message);
    }

    // Display flag for the row-menu Abort control. The abort action route
    // enforces write:runs independently. Permissive in OSS.
    const bulkAuth = await rbac.authenticateSession(request, {
      userId,
      organizationId: project.organizationId,
    });
    const { canAbort } = bulkAuth.ok
      ? checkPermissions(bulkAuth.ability, {
          canAbort: { action: "write", resource: { type: "runs" } },
        })
      : { canAbort: true };

    return typedjson({ ...data, canAbort });
  } catch (error) {
    console.error(error);
    throw new Response(undefined, {
      status: 400,
      statusText: "Something went wrong, if this problem persists please contact support.",
    });
  }
};

export default function Page() {
  const {
    bulkActions,
    currentPage,
    totalPages,
    totalCount: _totalCount,
    canAbort,
  } = useTypedLoaderData<typeof loader>();
  const organization = useOrganization();
  const project = useProject();
  const environment = useEnvironment();
  const { bulkActionParam } = useParams();
  const isShowingInspector = bulkActionParam !== undefined;

  return (
    <PageContainer>
      <NavBar>
        <PageTitle title="Bulk actions" />
        <PageAccessories>
          <AdminDebugTooltip />
          <LinkButton
            variant={"docs/small"}
            LeadingIcon={BookOpenIcon}
            to={docsPath("/bulk-actions")}
          >
            Bulk actions docs
          </LinkButton>
          <LinkButton
            variant="primary/small"
            LeadingIcon={PlusIcon}
            to={v3CreateBulkActionPath(organization, project, environment)}
            shortcut={{
              key: "n",
            }}
          >
            New bulk action
          </LinkButton>
        </PageAccessories>
      </NavBar>
      <PageBody scrollable={false}>
        {bulkActions.length === 0 ? (
          <MainCenteredContainer className="max-w-prose">
            <BulkActionsNone />
          </MainCenteredContainer>
        ) : (
          <ResizablePanelGroup orientation="horizontal" className="max-h-full">
            <ResizablePanel id="bulk-actions-main" min={"100px"}>
              <div
                className={cn(
                  "grid max-h-full min-h-full overflow-x-auto",
                  totalPages > 1 ? "grid-rows-[auto_1fr_auto]" : "grid-rows-[1fr]"
                )}
              >
                {totalPages > 1 && (
                  <div className="flex items-center justify-end gap-x-2 p-2">
                    <PaginationControls
                      currentPage={currentPage}
                      totalPages={totalPages}
                      showPageNumbers={false}
                    />
                  </div>
                )}

                <BulkActionsTable
                  bulkActions={bulkActions}
                  totalPages={totalPages}
                  canAbort={canAbort}
                />
                {totalPages > 1 && (
                  <div
                    className={cn(
                      "flex min-h-full",
                      totalPages > 1 && "justify-end border-t border-grid-dimmed px-2 py-3"
                    )}
                  >
                    <PaginationControls currentPage={currentPage} totalPages={totalPages} />
                  </div>
                )}
              </div>
            </ResizablePanel>
            <ResizableHandle
              id="bulk-actions-handle"
              className={collapsibleHandleClassName(isShowingInspector)}
            />
            <ResizablePanel
              id="bulk-actions-inspector"
              default="500px"
              min="500px"
              max="800px"
              className="overflow-hidden"
              collapsible
              collapsed={!isShowingInspector}
              onCollapseChange={() => {}}
              collapsedSize="0px"
              collapseAnimation={RESIZABLE_PANEL_ANIMATION}
            >
              <div className="h-full" style={{ minWidth: 500 }}>
                <Outlet />
              </div>
            </ResizablePanel>
          </ResizablePanelGroup>
        )}
      </PageBody>
    </PageContainer>
  );
}

function BulkActionsTable({
  bulkActions,
  totalPages,
  canAbort,
}: {
  bulkActions: BulkActionListItem[];
  totalPages: number;
  canAbort: boolean;
}) {
  const organization = useOrganization();
  const project = useProject();
  const environment = useEnvironment();
  const { bulkActionParam } = useParams();

  return (
    <Table containerClassName={cn(totalPages === 1 && "border-t-0")}>
      <TableHeader>
        <TableRow>
          <TableHeaderCell>ID</TableHeaderCell>
          <TableHeaderCell>Name</TableHeaderCell>
          <TableHeaderCell
            tooltip={
              <div className="flex flex-col divide-y divide-grid-dimmed">
                <div className="grid grid-cols-[8rem_1fr] gap-x-2 py-2 first:pt-1 last:pb-1">
                  <div className="mb-0.5 flex items-center gap-1.5 whitespace-nowrap">
                    <BulkActionStatusCombo status="PENDING" />
                  </div>
                  <Paragraph variant="extra-small" className="text-wrap! text-text-dimmed">
                    The bulk action is currently in progress. They can take some time if there are
                    lots of runs.
                  </Paragraph>
                </div>
                <div className="grid grid-cols-[8rem_1fr] gap-x-2 py-2 first:pt-1 last:pb-1">
                  <div className="mb-0.5 flex items-center gap-1.5 whitespace-nowrap">
                    <BulkActionStatusCombo status="COMPLETED" />
                  </div>
                  <Paragraph variant="extra-small" className="text-wrap! text-text-dimmed">
                    The bulk action has completed successfully.
                  </Paragraph>
                </div>
                <div className="grid grid-cols-[8rem_1fr] gap-x-2 py-2 first:pt-1 last:pb-1">
                  <div className="mb-0.5 flex items-center gap-1.5 whitespace-nowrap">
                    <BulkActionStatusCombo status="ABORTED" />
                  </div>
                  <Paragraph variant="extra-small" className="text-wrap! text-text-dimmed">
                    The bulk action was aborted.
                  </Paragraph>
                </div>
              </div>
            }
          >
            Status
          </TableHeaderCell>
          <TableHeaderCell>Bulk action</TableHeaderCell>
          <TableHeaderCell>Runs</TableHeaderCell>
          <TableHeaderCell>User</TableHeaderCell>
          <TableHeaderCell>Created</TableHeaderCell>
          <TableHeaderCell>Completed</TableHeaderCell>
          <TableHeaderCell>
            <span className="sr-only">Actions</span>
          </TableHeaderCell>
        </TableRow>
      </TableHeader>
      <TableBody>
        {bulkActions.length === 0 ? (
          <TableBlankRow colSpan={9}>There are no matching bulk actions</TableBlankRow>
        ) : (
          bulkActions.map((bulkAction) => {
            const path = v3BulkActionPath(organization, project, environment, bulkAction);
            const isSelected = bulkActionParam === bulkAction.friendlyId;

            return (
              <TableRow
                key={bulkAction.friendlyId}
                className={isSelected ? "bg-grid-dimmed" : undefined}
              >
                <TableCell to={path} isTabbableCell>
                  <TruncatedCopyableValue value={bulkAction.friendlyId} />
                </TableCell>
                <TableCell to={path}>{bulkAction.name || "–"}</TableCell>
                <TableCell to={path}>
                  <BulkActionStatusCombo status={bulkAction.status} />
                </TableCell>
                <TableCell to={path}>
                  <BulkActionTypeCombo type={bulkAction.type} />
                </TableCell>
                <TableCell to={path}>{bulkAction.totalCount}</TableCell>
                <TableCell to={path}>
                  {bulkAction.user ? (
                    <div className="flex items-center gap-1">
                      <UserAvatar
                        name={bulkAction.user.name}
                        avatarUrl={bulkAction.user.avatarUrl}
                        className="h-4 w-4"
                      />
                      <Paragraph variant="extra-small">{bulkAction.user.name}</Paragraph>
                    </div>
                  ) : (
                    "–"
                  )}
                </TableCell>
                <TableCell to={path}>
                  <DateTime date={bulkAction.createdAt} />
                </TableCell>
                <TableCell to={path}>
                  {bulkAction.completedAt ? <DateTime date={bulkAction.completedAt} /> : "–"}
                </TableCell>
                <BulkActionActionsCell bulkAction={bulkAction} path={path} canAbort={canAbort} />
              </TableRow>
            );
          })
        )}
      </TableBody>
    </Table>
  );
}

function BulkActionActionsCell({
  bulkAction,
  path,
  canAbort,
}: {
  bulkAction: BulkActionListItem;
  path: string;
  canAbort: boolean;
}) {
  // Abort is the only action, and only while the bulk action is still running.
  if (bulkAction.status !== "PENDING") {
    return <TableCell to={path}>{""}</TableCell>;
  }

  return (
    <TableCellMenu
      isSticky
      hiddenButtons={
        canAbort ? (
          <Dialog>
            <DialogTrigger asChild>
              <Button
                variant="minimal/small"
                LeadingIcon={NoSymbolIcon}
                leadingIconClassName="text-error"
              >
                <span className="text-text-bright">Abort…</span>
              </Button>
            </DialogTrigger>
            <AbortBulkActionDialog formAction={path} />
          </Dialog>
        ) : (
          <Button
            variant="minimal/small"
            LeadingIcon={NoSymbolIcon}
            leadingIconClassName="text-error"
            disabled
            tooltip="You don't have permission to abort bulk actions"
          >
            <span className="text-text-bright">Abort…</span>
          </Button>
        )
      }
    />
  );
}
