Skip to content
import { Head } from "fresh/runtime";
import { define, type SessionUser } from "../utils.ts";
import { deleteUser, setUserRole } from "@/lib/auth.ts";
import { type Account, listAccounts } from "@/lib/admin.ts";
import { CLASSES } from "@/lib/game.ts";
import { devAutoLoginEnabled } from "@/lib/dev-auth.ts";

export const handler = define.handlers({
  async GET(ctx) {
    if (!ctx.state.user) return ctx.redirect("/login");
    if (ctx.state.user.role !== "admin") {
      return new Response("Not found", { status: 404 });
    }
    return ctx.render(
      <AdminPage
        accounts={await listAccounts()}
        me={ctx.state.user}
        devMode={devAutoLoginEnabled()}
      />,
    );
  },
  async POST(ctx) {
    const me = ctx.state.user;
    if (!me || me.role !== "admin") {
      return new Response("Not found", { status: 404 });
    }
    const form = await ctx.req.formData();
    const action = String(form.get("action") ?? "");
    const userId = Number(form.get("userId"));
    if (Number.isInteger(userId) && userId > 0 && userId !== me.id) {
      if (action === "promote") await setUserRole(userId, "admin");
      else if (action === "demote") await setUserRole(userId, "user");
      else if (action === "delete") await deleteUser(userId);
    }
    return new Response(null, { status: 303, headers: { location: "/admin" } });
  },
});

function RoleForm(
  { userId, action, label, style }: {
    userId: number;
    action: string;
    label: string;
    style: string;
  },
) {
  return (
    <form method="post">
      <input type="hidden" name="userId" value={userId} />
      <button type="submit" name="action" value={action} class={style}>
        {label}
      </button>
    </form>
  );
}

function HeroCell({ hero }: { hero: Account["hero"] }) {
  if (!hero) {
    return (
      <span class="text-xs uppercase tracking-[0.2em] text-base-content/40">
        Unforged
      </span>
    );
  }
  const klass = CLASSES[hero.class];
  return (
    <div class="flex items-center gap-3">
      <span class="shield-mount size-9 shrink-0">
        <img
          src={klass.art}
          alt=""
          class={`shield mystic-class-icon mystic-class-${klass.id}`}
        />
      </span>
      <div class="min-w-0">
        <p class="font-display truncate text-sm tracking-[0.08em] text-accent">
          {hero.name}
        </p>
        <p class="text-[0.6rem] uppercase tracking-[0.2em] text-base-content/55">
          {klass.name}
        </p>
      </div>
    </div>
  );
}

function DeleteAction({ account }: { account: Account }) {
  const modalId = `banish-${account.id}`;
  return (
    <>
      <button
        type="button"
        popovertarget={modalId}
        class="btn btn-error btn-soft btn-xs"
      >
        Banish
      </button>
      <div class="modal" id={modalId} popover>
        {/* whitespace-normal: the popover lives inside a nowrap table cell. */}
        <div class="modal-box arch-panel vellum max-w-md whitespace-normal">
          <div class="corner-fleurons px-4 py-3">
            <h3 class="font-heading text-center text-lg uppercase tracking-[0.25em] text-error">
              Banish this soul?
            </h3>
            <div class="fleuron-rule my-3">
              <span aria-hidden="true">☾ ✧ ☽</span>
            </div>
            <p class="text-center text-sm text-base-content/75">
              <span class="text-accent">{account.email}</span>{" "}
              and every hero, card and record bound to it will be struck from
              the chronicle. This cannot be undone.
            </p>
            <div class="modal-action justify-center">
              <button
                type="button"
                popovertarget={modalId}
                popovertargetaction="hide"
                class="btn btn-ghost btn-sm"
              >
                Spare them
              </button>
              <RoleForm
                userId={account.id}
                action="delete"
                label="Banish"
                style="btn btn-error iron-band btn-sm"
              />
            </div>
          </div>
        </div>
        <div class="modal-backdrop">
          <button
            type="button"
            popovertarget={modalId}
            popovertargetaction="hide"
          >
            Close
          </button>
        </div>
      </div>
    </>
  );
}

export function AdminPage(
  { accounts, me, devMode }: {
    accounts: Account[];
    me: SessionUser;
    devMode: boolean;
  },
) {
  const admins = accounts.filter((a) => a.role === "admin").length;
  const heroes = accounts.filter((a) => a.hero).length;

  return (
    <div class="tapestry min-h-dvh flex flex-col">
      <Head>
        <title>Profectus — Administration</title>
      </Head>

      <div class="navbar rivets min-h-16 border-b-0 px-3 sm:px-4">
        <div class="navbar-start">
          <span class="font-gothic tracking-[0.18em] text-accent text-lg sm:text-xl">
            Profectus
          </span>
        </div>
        <div class="navbar-center">
          <span class="text-xs uppercase tracking-[0.3em] text-base-content/65">
            Chronicle of Souls
          </span>
        </div>
        <div class="navbar-end gap-2">
          <a href="/" class="btn btn-ghost btn-sm">Back to game</a>
          <a href="/logout" class="btn btn-ghost btn-sm hidden sm:inline-flex">
            Log out
          </a>
        </div>
      </div>

      <main class="margin-vines flex-1 px-4 py-10 sm:py-14">
        <div class="relative z-1 mx-auto w-full max-w-5xl">
          <header class="text-center mb-8">
            <h1 class="font-gothic text-4xl md:text-5xl tracking-[0.16em] text-accent">
              Administration
            </h1>
            <div class="fleuron-rule mt-4 w-72 mx-auto">
              <span aria-hidden="true">☾ ✦ ☽</span>
            </div>
          </header>

          {devMode && (
            <div role="alert" class="alert alert-warning alert-soft mb-8">
              <span class="text-sm">
                Local development: unauthenticated visitors are signed in
                automatically as <span class="font-bold">{me.email}</span>. Set
                {" "}
                <code class="kbd kbd-sm">DEV_AUTOLOGIN=off</code>{" "}
                to disable this.
              </span>
            </div>
          )}

          <div class="stats stats-vertical sm:stats-horizontal w-full mb-8 bg-transparent">
            <div class="stat place-items-center">
              <div class="stat-title text-[0.6rem] uppercase tracking-[0.3em]">
                Souls
              </div>
              <div class="stat-value font-display text-primary text-3xl">
                {accounts.length}
              </div>
            </div>
            <div class="stat place-items-center">
              <div class="stat-title text-[0.6rem] uppercase tracking-[0.3em]">
                Heroes forged
              </div>
              <div class="stat-value font-display text-accent text-3xl">
                {heroes}
              </div>
            </div>
            <div class="stat place-items-center">
              <div class="stat-title text-[0.6rem] uppercase tracking-[0.3em]">
                Wardens
              </div>
              <div class="stat-value font-display text-secondary text-3xl">
                {admins}
              </div>
            </div>
          </div>

          <section class="arch-panel vellum card">
            <div class="corner-fleurons card-body p-4 sm:p-6">
              <div class="overflow-x-auto">
                <table class="table table-sm md:table-md">
                  <thead>
                    <tr class="text-[0.6rem] uppercase tracking-[0.25em] text-base-content/60">
                      <th>Account</th>
                      <th>Hero</th>
                      <th class="text-right">Level</th>
                      <th class="text-right">Anima</th>
                      <th class="text-right">Cards</th>
                      <th>Standing</th>
                      <th class="text-right">Rites</th>
                    </tr>
                  </thead>
                  <tbody>
                    {accounts.map((account) => {
                      const isSelf = account.id === me.id;
                      return (
                        <tr key={account.id}>
                          <td>
                            <p class="text-sm text-base-content/90">
                              {account.email}
                            </p>
                            <p class="text-[0.6rem] uppercase tracking-[0.2em] text-base-content/45">
                              Joined {account.createdAt}
                            </p>
                          </td>
                          <td>
                            <HeroCell hero={account.hero} />
                          </td>
                          <td class="text-right font-display text-primary">
                            {account.hero?.level ?? "—"}
                          </td>
                          <td class="text-right">
                            {account.hero
                              ? (
                                <span class="badge badge-sm anima-coin font-bold whitespace-nowrap">
                                  {account.hero.anima} ✦
                                </span>
                              )
                              : "—"}
                          </td>
                          <td class="text-right font-display text-accent">
                            {account.hero?.deckSize ?? "—"}
                          </td>
                          <td>
                            <div class="flex items-center gap-2">
                              <span
                                class={`badge badge-sm ${
                                  account.role === "admin"
                                    ? "badge-primary"
                                    : "badge-ghost"
                                }`}
                              >
                                {account.role === "admin" ? "Warden" : "Seeker"}
                              </span>
                              {isSelf && (
                                <span class="badge badge-sm badge-outline">
                                  you
                                </span>
                              )}
                            </div>
                          </td>
                          <td class="text-right whitespace-nowrap">
                            {isSelf
                              ? <span class="text-base-content/40">—</span>
                              : (
                                <div class="inline-flex items-center gap-2">
                                  {account.role === "admin"
                                    ? (
                                      <RoleForm
                                        userId={account.id}
                                        action="demote"
                                        label="Demote"
                                        style="btn btn-ghost btn-xs"
                                      />
                                    )
                                    : (
                                      <RoleForm
                                        userId={account.id}
                                        action="promote"
                                        label="Anoint"
                                        style="btn btn-ghost btn-xs"
                                      />
                                    )}
                                  <DeleteAction account={account} />
                                </div>
                              )}
                          </td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
            </div>
          </section>
        </div>
      </main>
    </div>
  );
}

export default AdminPage;