import { Hono } from "hono";
import { BaseLayout } from "../layouts/base.js";
import { PageHeader, Card, EmptyState, ErrorDisplay } from "../components/index.js";
import {
  getSessionWithPermissions,
  hasAnyAdminPermission,
  canManageMembers,
  canManageCategories,
  canViewModLog,
  canManageRoles,
} from "../lib/session.js";
import { isProgrammingError } from "../lib/errors.js";
import { logger } from "../lib/logger.js";

// ─── Types ─────────────────────────────────────────────────────────────────

interface MemberEntry {
  did: string;
  handle: string;
  role: string;
  roleUri: string | null;
  joinedAt: string | null;
}

interface RoleEntry {
  id: string;
  name: string;
  uri: string;
  priority: number;
}

interface CategoryEntry {
  id: string;
  did: string;
  uri: string;
  name: string;
  description: string | null;
  sortOrder: number | null;
}

interface BoardEntry {
  id: string;
  name: string;
  description: string | null;
  sortOrder: number | null;
  categoryUri: string;
  uri: string;
}

// ─── Helpers ───────────────────────────────────────────────────────────────

function formatJoinedDate(isoString: string | null): string {
  if (!isoString) return "—";
  const d = new Date(isoString);
  if (isNaN(d.getTime())) return "—";
  return d.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });
}

// ─── Components ────────────────────────────────────────────────────────────

function MemberRow({
  member,
  roles,
  showRoleControls,
  errorMsg = null,
}: {
  member: MemberEntry;
  roles: RoleEntry[];
  showRoleControls: boolean;
  errorMsg?: string | null;
}) {
  return (
    <tr>
      <td>{member.handle}</td>
      <td>
        <span class="role-badge">{member.role}</span>
      </td>
      <td>{formatJoinedDate(member.joinedAt)}</td>
      {showRoleControls ? (
        <td>
          <form
            hx-post={`/admin/members/${member.did}/role`}
            hx-target="closest tr"
            hx-swap="outerHTML"
          >
            <input type="hidden" name="handle" value={member.handle} />
            <input type="hidden" name="joinedAt" value={member.joinedAt ?? ""} />
            <input type="hidden" name="currentRole" value={member.role} />
            <input type="hidden" name="currentRoleUri" value={member.roleUri ?? ""} />
            <input type="hidden" name="rolesJson" value={JSON.stringify(roles)} />
            <div class="member-row__assign-form">
              <label class="sr-only" for={`role-${member.did}`}>
                Assign role to {member.handle}
              </label>
              <select id={`role-${member.did}`} name="roleUri">
                {roles.map((role) => (
                  <option value={role.uri} selected={member.roleUri === role.uri}>
                    {role.name}
                  </option>
                ))}
              </select>
              <button type="submit" class="btn btn-primary">
                Assign
              </button>
            </div>
            {errorMsg && <span class="member-row__error">{errorMsg}</span>}
          </form>
        </td>
      ) : (
        errorMsg && (
          <td>
            <span class="member-row__error">{errorMsg}</span>
          </td>
        )
      )}
    </tr>
  );
}

// ─── Private Helpers ────────────────────────────────────────────────────────

/**
 * Extracts the error message from an AppView error response.
 * Falls back to the provided default if JSON parsing fails.
 */
async function extractAppviewError(res: Response, fallback: string): Promise<string> {
  try {
    const data = (await res.json()) as { error?: string };
    return data.error ?? fallback;
  } catch {
    return fallback;
  }
}

/**
 * Parses a sort order value from a form field string.
 * Returns 0 for empty/missing values, null for invalid values (negative or non-integer).
 */
function parseSortOrder(value: unknown): number | null {
  if (typeof value !== "string" || value.trim() === "") return 0;
  const n = Number(value);
  return Number.isInteger(n) && n >= 0 ? n : null;
}

// ─── Routes ────────────────────────────────────────────────────────────────

export function createAdminRoutes(appviewUrl: string) {
  const app = new Hono();

  // ─── Structure Page Components ──────────────────────────────────────────

  function StructureBoardRow({ board }: { board: BoardEntry }) {
    const dialogId = `confirm-delete-board-${board.id}`;
    return (
      <div class="structure-board">
        <div class="structure-board__header">
          <span class="structure-board__name">{board.name}</span>
          <span class="structure-board__meta">sortOrder: {board.sortOrder ?? 0}</span>
          <div class="structure-board__actions">
            <button
              type="button"
              class="btn btn-secondary btn-sm"
              onclick={`document.getElementById('edit-board-${board.id}').open=!document.getElementById('edit-board-${board.id}').open`}
            >
              Edit
            </button>
            <button
              type="button"
              class="btn btn-danger btn-sm"
              onclick={`document.getElementById('${dialogId}').showModal()`}
            >
              Delete
            </button>
          </div>
        </div>
        <details id={`edit-board-${board.id}`} class="structure-edit-form">
          <summary class="sr-only">Edit {board.name}</summary>
          <form method="post" action={`/admin/structure/boards/${board.id}/edit`} class="structure-edit-form__body">
            <div class="form-group">
              <label for={`edit-board-name-${board.id}`}>Name</label>
              <input id={`edit-board-name-${board.id}`} type="text" name="name" value={board.name} required />
            </div>
            <div class="form-group">
              <label for={`edit-board-desc-${board.id}`}>Description</label>
              <textarea id={`edit-board-desc-${board.id}`} name="description">{board.description ?? ""}</textarea>
            </div>
            <div class="form-group">
              <label for={`edit-board-sort-${board.id}`}>Sort Order</label>
              <input id={`edit-board-sort-${board.id}`} type="number" name="sortOrder" min="0" value={String(board.sortOrder ?? 0)} />
            </div>
            <button type="submit" class="btn btn-primary">Save Changes</button>
          </form>
        </details>
        <dialog id={dialogId} class="structure-confirm-dialog">
          <p>Delete board &quot;{board.name}&quot;? This cannot be undone.</p>
          <form method="post" action={`/admin/structure/boards/${board.id}/delete`} class="dialog-actions">
            <button type="submit" class="btn btn-danger">Delete</button>
            <button
              type="button"
              class="btn btn-secondary"
              onclick={`document.getElementById('${dialogId}').close()`}
            >
              Cancel
            </button>
          </form>
        </dialog>
      </div>
    );
  }

  function StructureCategorySection({
    category,
    boards,
  }: {
    category: CategoryEntry;
    boards: BoardEntry[];
  }) {
    const dialogId = `confirm-delete-category-${category.id}`;
    return (
      <div class="structure-category">
        <div class="structure-category__header">
          <span class="structure-category__name">{category.name}</span>
          <span class="structure-category__meta">sortOrder: {category.sortOrder ?? 0}</span>
          <div class="structure-category__actions">
            <button
              type="button"
              class="btn btn-secondary btn-sm"
              onclick={`document.getElementById('edit-category-${category.id}').open=!document.getElementById('edit-category-${category.id}').open`}
            >
              Edit
            </button>
            <button
              type="button"
              class="btn btn-danger btn-sm"
              onclick={`document.getElementById('${dialogId}').showModal()`}
            >
              Delete
            </button>
          </div>
        </div>

        <details id={`edit-category-${category.id}`} class="structure-edit-form">
          <summary class="sr-only">Edit {category.name}</summary>
          <form method="post" action={`/admin/structure/categories/${category.id}/edit`} class="structure-edit-form__body">
            <div class="form-group">
              <label for={`edit-cat-name-${category.id}`}>Name</label>
              <input id={`edit-cat-name-${category.id}`} type="text" name="name" value={category.name} required />
            </div>
            <div class="form-group">
              <label for={`edit-cat-desc-${category.id}`}>Description</label>
              <textarea id={`edit-cat-desc-${category.id}`} name="description">{category.description ?? ""}</textarea>
            </div>
            <div class="form-group">
              <label for={`edit-cat-sort-${category.id}`}>Sort Order</label>
              <input id={`edit-cat-sort-${category.id}`} type="number" name="sortOrder" min="0" value={String(category.sortOrder ?? 0)} />
            </div>
            <button type="submit" class="btn btn-primary">Save Changes</button>
          </form>
        </details>

        <dialog id={dialogId} class="structure-confirm-dialog">
          <p>Delete category &quot;{category.name}&quot;? All boards must be removed first.</p>
          <form method="post" action={`/admin/structure/categories/${category.id}/delete`} class="dialog-actions">
            <button type="submit" class="btn btn-danger">Delete</button>
            <button
              type="button"
              class="btn btn-secondary"
              onclick={`document.getElementById('${dialogId}').close()`}
            >
              Cancel
            </button>
          </form>
        </dialog>

        <div class="structure-boards">
          {boards.map((board) => (
            <StructureBoardRow board={board} />
          ))}
          <details class="structure-add-board">
            <summary class="structure-add-board__trigger">+ Add Board</summary>
            <form method="post" action="/admin/structure/boards" class="structure-edit-form__body">
              <input type="hidden" name="categoryUri" value={category.uri} />
              <div class="form-group">
                <label for={`new-board-name-${category.id}`}>Name</label>
                <input id={`new-board-name-${category.id}`} type="text" name="name" required />
              </div>
              <div class="form-group">
                <label for={`new-board-desc-${category.id}`}>Description</label>
                <textarea id={`new-board-desc-${category.id}`} name="description"></textarea>
              </div>
              <div class="form-group">
                <label for={`new-board-sort-${category.id}`}>Sort Order</label>
                <input id={`new-board-sort-${category.id}`} type="number" name="sortOrder" min="0" value="0" />
              </div>
              <button type="submit" class="btn btn-primary">Add Board</button>
            </form>
          </details>
        </div>
      </div>
    );
  }

  // ── GET /admin ────────────────────────────────────────────────────────────

  app.get("/admin", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));

    if (!auth.authenticated) {
      return c.redirect("/login");
    }

    if (!hasAnyAdminPermission(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Access Denied" />
          <p>You don&apos;t have permission to access the admin panel.</p>
        </BaseLayout>,
        403
      );
    }

    const showMembers = canManageMembers(auth);
    const showStructure = canManageCategories(auth);
    const showModLog = canViewModLog(auth);

    return c.html(
      <BaseLayout title="Admin Panel — atBB Forum" auth={auth}>
        <PageHeader title="Admin Panel" />
        <div class="admin-nav-grid">
          {showMembers && (
            <a href="/admin/members" class="admin-nav-card">
              <Card>
                <p class="admin-nav-card__icon" aria-hidden="true">👥</p>
                <p class="admin-nav-card__title">Members</p>
                <p class="admin-nav-card__description">View and assign member roles</p>
              </Card>
            </a>
          )}
          {showStructure && (
            <a href="/admin/structure" class="admin-nav-card">
              <Card>
                <p class="admin-nav-card__icon" aria-hidden="true">📁</p>
                <p class="admin-nav-card__title">Structure</p>
                <p class="admin-nav-card__description">Manage categories and boards</p>
              </Card>
            </a>
          )}
          {showModLog && (
            <a href="/admin/modlog" class="admin-nav-card">
              <Card>
                <p class="admin-nav-card__icon" aria-hidden="true">📋</p>
                <p class="admin-nav-card__title">Mod Log</p>
                <p class="admin-nav-card__description">Audit trail of moderation actions</p>
              </Card>
            </a>
          )}
        </div>
      </BaseLayout>
    );
  });

  // ── GET /admin/members ────────────────────────────────────────────────────

  app.get("/admin/members", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));

    if (!auth.authenticated) {
      return c.redirect("/login");
    }

    if (!canManageMembers(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Members" />
          <p>You don&apos;t have permission to manage members.</p>
        </BaseLayout>,
        403
      );
    }

    const cookie = c.req.header("cookie") ?? "";
    const showRoleControls = canManageRoles(auth);

    let membersRes: Response;
    let rolesRes: Response | null = null;

    try {
      [membersRes, rolesRes] = await Promise.all([
        fetch(`${appviewUrl}/api/admin/members`, { headers: { Cookie: cookie } }),
        showRoleControls
          ? fetch(`${appviewUrl}/api/admin/roles`, { headers: { Cookie: cookie } })
          : Promise.resolve(null),
      ]);
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error fetching members", {
        operation: "GET /admin/members",
        error: error instanceof Error ? error.message : String(error),
      });
      return c.html(
        <BaseLayout title="Members — atBB Forum" auth={auth}>
          <PageHeader title="Members" />
          <ErrorDisplay
            message="Unable to load members"
            detail="The forum is temporarily unavailable. Please try again."
          />
        </BaseLayout>,
        503
      );
    }

    if (!membersRes.ok) {
      if (membersRes.status === 401) {
        return c.redirect("/login");
      }
      logger.error("AppView returned error for members list", {
        operation: "GET /admin/members",
        status: membersRes.status,
      });
      return c.html(
        <BaseLayout title="Members — atBB Forum" auth={auth}>
          <PageHeader title="Members" />
          <ErrorDisplay
            message="Something went wrong"
            detail="Could not load member list. Please try again."
          />
        </BaseLayout>,
        500
      );
    }

    const membersData = (await membersRes.json()) as {
      members: MemberEntry[];
      isTruncated: boolean;
    };
    let rolesData: { roles: RoleEntry[] } | null = null;
    if (rolesRes?.ok) {
      try {
        rolesData = (await rolesRes.json()) as { roles: RoleEntry[] };
      } catch (error) {
        if (!(error instanceof SyntaxError)) throw error;
        logger.error("Malformed JSON from AppView roles response", {
          operation: "GET /admin/members",
        });
      }
    } else if (rolesRes) {
      logger.error("AppView returned error for roles list", {
        operation: "GET /admin/members",
        status: rolesRes.status,
      });
    }

    const members = membersData.members;
    const roles = rolesData?.roles ?? [];
    const isTruncated = membersData.isTruncated;
    const title = `Members (${members.length}${isTruncated ? "+" : ""})`;

    return c.html(
      <BaseLayout title="Members — atBB Forum" auth={auth}>
        <PageHeader title={title} />
        {members.length === 0 ? (
          <EmptyState message="No members yet" />
        ) : (
          <div class="card">
            <table class="admin-member-table">
              <thead>
                <tr>
                  <th scope="col">Handle</th>
                  <th scope="col">Role</th>
                  <th scope="col">Joined</th>
                  {showRoleControls && <th scope="col">Assign Role</th>}
                </tr>
              </thead>
              <tbody>
                {members.map((member) => (
                  <MemberRow
                    member={member}
                    roles={roles}
                    showRoleControls={showRoleControls}
                  />
                ))}
              </tbody>
            </table>
          </div>
        )}
      </BaseLayout>
    );
  });

  // ── POST /admin/members/:did/role (HTMX proxy) ────────────────────────────

  app.post("/admin/members/:did/role", async (c) => {
    // Permission gate — must come before body parsing
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) {
      return c.html(
        <tr>
          <td colspan={4}>
            <span class="member-row__error">You must be logged in to perform this action.</span>
          </td>
        </tr>,
        401
      );
    }
    if (!canManageRoles(auth)) {
      return c.html(
        <tr>
          <td colspan={4}>
            <span class="member-row__error">You don&apos;t have permission to assign roles.</span>
          </td>
        </tr>,
        403
      );
    }

    const targetDid = c.req.param("did");
    const cookie = c.req.header("cookie") ?? "";

    let body: Record<string, string | File>;
    try {
      body = await c.req.parseBody();
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Failed to parse form body", {
        operation: "POST /admin/members/:did/role",
        targetDid,
      });
      return c.html(
        <tr>
          <td colspan={4}>
            <span class="member-row__error">Invalid form submission.</span>
          </td>
        </tr>
      );
    }

    const roleUri = typeof body.roleUri === "string" ? body.roleUri.trim() : "";
    const handle = typeof body.handle === "string" ? body.handle : targetDid;
    const joinedAt = typeof body.joinedAt === "string" && body.joinedAt ? body.joinedAt : null;
    const currentRole = typeof body.currentRole === "string" ? body.currentRole : "";
    const currentRoleUri =
      typeof body.currentRoleUri === "string" && body.currentRoleUri
        ? body.currentRoleUri
        : null;
    const showRoleControls = canManageRoles(auth);

    let roles: RoleEntry[] = [];
    try {
      const rolesJson = typeof body.rolesJson === "string" ? body.rolesJson : "[]";
      roles = JSON.parse(rolesJson) as RoleEntry[];
    } catch (error) {
      if (!(error instanceof SyntaxError)) throw error;
      logger.warn("Malformed rolesJson in POST body", {
        operation: "POST /admin/members/:did/role",
        targetDid,
      });
      return c.html(
        <MemberRow
          member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }}
          roles={[]}
          showRoleControls={canManageRoles(auth)}
          errorMsg="Role data was corrupted. Please reload the page."
        />
      );
    }

    if (!roleUri) {
      return c.html(
        <MemberRow
          member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }}
          roles={roles}
          showRoleControls={showRoleControls}
          errorMsg="Please select a role."
        />
      );
    }

    if (!targetDid.startsWith("did:")) {
      return c.html(
        <MemberRow
          member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }}
          roles={roles}
          showRoleControls={showRoleControls}
          errorMsg="Invalid member identifier."
        />
      );
    }

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/members/${targetDid}/role`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Cookie: cookie,
        },
        body: JSON.stringify({ roleUri }),
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error proxying role assignment", {
        operation: "POST /admin/members/:did/role",
        targetDid,
        error: error instanceof Error ? error.message : String(error),
      });
      return c.html(
        <MemberRow
          member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }}
          roles={roles}
          showRoleControls={showRoleControls}
          errorMsg="Forum temporarily unavailable. Please try again."
        />
      );
    }

    if (appviewRes.ok) {
      let data: { roleAssigned: string; targetDid: string };
      try {
        data = (await appviewRes.json()) as { roleAssigned: string; targetDid: string };
      } catch (error) {
        if (!(error instanceof SyntaxError)) throw error;
        logger.error("Malformed JSON from AppView role assignment response", {
          operation: "POST /admin/members/:did/role",
          targetDid,
        });
        return c.html(
          <MemberRow
            member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }}
            roles={roles}
            showRoleControls={showRoleControls}
            errorMsg="Something went wrong. Please try again."
          />
        );
      }
      const newRoleName = data.roleAssigned || currentRole;
      return c.html(
        <MemberRow
          member={{ did: targetDid, handle, role: newRoleName, roleUri, joinedAt }}
          roles={roles}
          showRoleControls={showRoleControls}
        />
      );
    }

    let errorMsg: string;
    if (appviewRes.status === 403) {
      errorMsg = "Cannot assign a role with equal or higher authority than your own.";
    } else if (appviewRes.status === 404) {
      errorMsg = "Member or role not found.";
    } else if (appviewRes.status === 401) {
      errorMsg = "Your session has expired. Please log in again.";
    } else {
      logger.error("AppView returned error for role assignment", {
        operation: "POST /admin/members/:did/role",
        targetDid,
        status: appviewRes.status,
      });
      errorMsg = "Something went wrong. Please try again.";
    }

    return c.html(
      <MemberRow
        member={{ did: targetDid, handle, role: currentRole, roleUri: currentRoleUri, joinedAt }}
        roles={roles}
        showRoleControls={showRoleControls}
        errorMsg={errorMsg}
      />
    );
  });

  // ── GET /admin/structure ─────────────────────────────────────────────────

  app.get("/admin/structure", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));

    if (!auth.authenticated) {
      return c.redirect("/login");
    }

    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const cookie = c.req.header("cookie") ?? "";
    const errorMsg = c.req.query("error") ?? null;

    let categoriesRes: Response;
    try {
      categoriesRes = await fetch(`${appviewUrl}/api/categories`, {
        headers: { Cookie: cookie },
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error fetching categories for structure page", {
        operation: "GET /admin/structure",
        error: error instanceof Error ? error.message : String(error),
      });
      return c.html(
        <BaseLayout title="Forum Structure — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <ErrorDisplay
            message="Unable to load forum structure"
            detail="The forum is temporarily unavailable. Please try again."
          />
        </BaseLayout>,
        503
      );
    }

    if (!categoriesRes.ok) {
      if (categoriesRes.status === 401) {
        return c.redirect("/login");
      }
      logger.error("AppView returned error for categories list", {
        operation: "GET /admin/structure",
        status: categoriesRes.status,
      });
      return c.html(
        <BaseLayout title="Forum Structure — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <ErrorDisplay
            message="Something went wrong"
            detail="Could not load forum structure. Please try again."
          />
        </BaseLayout>,
        500
      );
    }

    const categoriesData = (await categoriesRes.json()) as { categories: CategoryEntry[] };
    const catList = categoriesData.categories;

    // Fetch boards for each category in parallel (N+1 pattern — same as home.tsx)
    let boardsPerCategory: BoardEntry[][];
    try {
      boardsPerCategory = await Promise.all(
        catList.map((cat) =>
          fetch(`${appviewUrl}/api/categories/${cat.id}/boards`, {
            headers: { Cookie: cookie },
          })
            .then((r) => r.json() as Promise<{ boards: BoardEntry[] }>)
            .then((data) => data.boards)
            .catch((error) => {
              if (isProgrammingError(error)) throw error;
              logger.error("Failed to fetch boards for category", {
                operation: "GET /admin/structure",
                categoryId: cat.id,
                error: error instanceof Error ? error.message : String(error),
              });
              return [] as BoardEntry[];
            })
        )
      );
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Failed to fetch boards for all categories", {
        operation: "GET /admin/structure",
        error: error instanceof Error ? error.message : String(error),
      });
      boardsPerCategory = catList.map(() => []);
    }

    const structure = catList.map((cat, i) => ({
      category: cat,
      boards: boardsPerCategory[i] ?? [],
    }));

    return c.html(
      <BaseLayout title="Forum Structure — atBB Forum" auth={auth}>
        <PageHeader title="Forum Structure" />
        {errorMsg && <div class="structure-error-banner">{errorMsg}</div>}
        <div class="structure-page">
          {structure.length === 0 ? (
            <EmptyState message="No categories yet" />
          ) : (
            structure.map(({ category, boards }) => (
              <StructureCategorySection category={category} boards={boards} />
            ))
          )}
          <div class="structure-add-category card">
            <h3>Add Category</h3>
            <form method="post" action="/admin/structure/categories">
              <div class="form-group">
                <label for="new-cat-name">Name</label>
                <input id="new-cat-name" type="text" name="name" required />
              </div>
              <div class="form-group">
                <label for="new-cat-desc">Description</label>
                <textarea id="new-cat-desc" name="description"></textarea>
              </div>
              <div class="form-group">
                <label for="new-cat-sort">Sort Order</label>
                <input id="new-cat-sort" type="number" name="sortOrder" min="0" value="0" />
              </div>
              <button type="submit" class="btn btn-primary">Add Category</button>
            </form>
          </div>
        </div>
      </BaseLayout>
    );
  });

  // ── POST /admin/structure/categories ─────────────────────────────────────

  app.post("/admin/structure/categories", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) return c.redirect("/login");
    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const cookie = c.req.header("cookie") ?? "";

    let body: Record<string, string | File>;
    try {
      body = await c.req.parseBody();
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Invalid form submission.")}`,
        302
      );
    }

    const name = typeof body.name === "string" ? body.name.trim() : "";
    if (!name) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Category name is required.")}`,
        302
      );
    }

    const description = typeof body.description === "string" ? body.description.trim() || null : null;
    const sortOrder = parseSortOrder(body.sortOrder);
    if (sortOrder === null) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Sort order must be a non-negative integer.")}`,
        302
      );
    }

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/categories`, {
        method: "POST",
        headers: { "Content-Type": "application/json", Cookie: cookie },
        body: JSON.stringify({ name, description, sortOrder }),
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error creating category", {
        operation: "POST /admin/structure/categories",
        error: error instanceof Error ? error.message : String(error),
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`,
        302
      );
    }

    if (!appviewRes.ok) {
      const msg = await extractAppviewError(appviewRes, "Failed to create category. Please try again.");
      logger.error("AppView error creating category", {
        operation: "POST /admin/structure/categories",
        status: appviewRes.status,
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent(msg)}`,
        302
      );
    }

    return c.redirect("/admin/structure", 302);
  });

  // ── POST /admin/structure/categories/:id/edit ─────────────────────────────

  app.post("/admin/structure/categories/:id/edit", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) return c.redirect("/login");
    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const categoryId = c.req.param("id");
    const cookie = c.req.header("cookie") ?? "";

    let body: Record<string, string | File>;
    try {
      body = await c.req.parseBody();
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Invalid form submission.")}`,
        302
      );
    }

    const name = typeof body.name === "string" ? body.name.trim() : "";
    if (!name) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Category name is required.")}`,
        302
      );
    }

    const description = typeof body.description === "string" ? body.description.trim() || null : null;
    const sortOrder = parseSortOrder(body.sortOrder);
    if (sortOrder === null) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Sort order must be a non-negative integer.")}`,
        302
      );
    }

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/categories/${categoryId}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", Cookie: cookie },
        body: JSON.stringify({ name, description, sortOrder }),
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error editing category", {
        operation: "POST /admin/structure/categories/:id/edit",
        categoryId,
        error: error instanceof Error ? error.message : String(error),
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`,
        302
      );
    }

    if (!appviewRes.ok) {
      const msg = await extractAppviewError(appviewRes, "Failed to update category. Please try again.");
      logger.error("AppView error editing category", {
        operation: "POST /admin/structure/categories/:id/edit",
        categoryId,
        status: appviewRes.status,
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent(msg)}`,
        302
      );
    }

    return c.redirect("/admin/structure", 302);
  });

  // ── POST /admin/structure/categories/:id/delete ───────────────────────────

  app.post("/admin/structure/categories/:id/delete", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) return c.redirect("/login");
    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const categoryId = c.req.param("id");
    const cookie = c.req.header("cookie") ?? "";

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/categories/${categoryId}`, {
        method: "DELETE",
        headers: { Cookie: cookie },
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error deleting category", {
        operation: "POST /admin/structure/categories/:id/delete",
        categoryId,
        error: error instanceof Error ? error.message : String(error),
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`,
        302
      );
    }

    if (!appviewRes.ok) {
      const msg = await extractAppviewError(appviewRes, "Failed to delete category. Please try again.");
      logger.error("AppView error deleting category", {
        operation: "POST /admin/structure/categories/:id/delete",
        categoryId,
        status: appviewRes.status,
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent(msg)}`,
        302
      );
    }

    return c.redirect("/admin/structure", 302);
  });

  // ── POST /admin/structure/boards ──────────────────────────────────────────

  app.post("/admin/structure/boards", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) return c.redirect("/login");
    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const cookie = c.req.header("cookie") ?? "";

    let body: Record<string, string | File>;
    try {
      body = await c.req.parseBody();
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Invalid form submission.")}`,
        302
      );
    }

    const name = typeof body.name === "string" ? body.name.trim() : "";
    if (!name) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Board name is required.")}`,
        302
      );
    }

    const categoryUri = typeof body.categoryUri === "string" ? body.categoryUri.trim() : "";
    if (!categoryUri) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Category is required to create a board.")}`,
        302
      );
    }

    const description = typeof body.description === "string" ? body.description.trim() || null : null;
    const sortOrder = parseSortOrder(body.sortOrder);
    if (sortOrder === null) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Sort order must be a non-negative integer.")}`,
        302
      );
    }

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/boards`, {
        method: "POST",
        headers: { "Content-Type": "application/json", Cookie: cookie },
        body: JSON.stringify({ name, description, sortOrder, categoryUri }),
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error creating board", {
        operation: "POST /admin/structure/boards",
        error: error instanceof Error ? error.message : String(error),
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`,
        302
      );
    }

    if (!appviewRes.ok) {
      const msg = await extractAppviewError(appviewRes, "Failed to create board. Please try again.");
      logger.error("AppView error creating board", {
        operation: "POST /admin/structure/boards",
        status: appviewRes.status,
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent(msg)}`,
        302
      );
    }

    return c.redirect("/admin/structure", 302);
  });

  // ── POST /admin/structure/boards/:id/edit ─────────────────────────────────

  app.post("/admin/structure/boards/:id/edit", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) return c.redirect("/login");
    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const boardId = c.req.param("id");
    const cookie = c.req.header("cookie") ?? "";

    let body: Record<string, string | File>;
    try {
      body = await c.req.parseBody();
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Invalid form submission.")}`,
        302
      );
    }

    const name = typeof body.name === "string" ? body.name.trim() : "";
    if (!name) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Board name is required.")}`,
        302
      );
    }

    const description = typeof body.description === "string" ? body.description.trim() || null : null;
    const sortOrder = parseSortOrder(body.sortOrder);
    if (sortOrder === null) {
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Sort order must be a non-negative integer.")}`,
        302
      );
    }

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/boards/${boardId}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json", Cookie: cookie },
        body: JSON.stringify({ name, description, sortOrder }),
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error editing board", {
        operation: "POST /admin/structure/boards/:id/edit",
        boardId,
        error: error instanceof Error ? error.message : String(error),
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`,
        302
      );
    }

    if (!appviewRes.ok) {
      const msg = await extractAppviewError(appviewRes, "Failed to update board. Please try again.");
      logger.error("AppView error editing board", {
        operation: "POST /admin/structure/boards/:id/edit",
        boardId,
        status: appviewRes.status,
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent(msg)}`,
        302
      );
    }

    return c.redirect("/admin/structure", 302);
  });

  // ── POST /admin/structure/boards/:id/delete ───────────────────────────────

  app.post("/admin/structure/boards/:id/delete", async (c) => {
    const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie"));
    if (!auth.authenticated) return c.redirect("/login");
    if (!canManageCategories(auth)) {
      return c.html(
        <BaseLayout title="Access Denied — atBB Forum" auth={auth}>
          <PageHeader title="Forum Structure" />
          <p>You don&apos;t have permission to manage forum structure.</p>
        </BaseLayout>,
        403
      );
    }

    const boardId = c.req.param("id");
    const cookie = c.req.header("cookie") ?? "";

    let appviewRes: Response;
    try {
      appviewRes = await fetch(`${appviewUrl}/api/admin/boards/${boardId}`, {
        method: "DELETE",
        headers: { Cookie: cookie },
      });
    } catch (error) {
      if (isProgrammingError(error)) throw error;
      logger.error("Network error deleting board", {
        operation: "POST /admin/structure/boards/:id/delete",
        boardId,
        error: error instanceof Error ? error.message : String(error),
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`,
        302
      );
    }

    if (!appviewRes.ok) {
      const msg = await extractAppviewError(appviewRes, "Failed to delete board. Please try again.");
      logger.error("AppView error deleting board", {
        operation: "POST /admin/structure/boards/:id/delete",
        boardId,
        status: appviewRes.status,
      });
      return c.redirect(
        `/admin/structure?error=${encodeURIComponent(msg)}`,
        302
      );
    }

    return c.redirect("/admin/structure", 302);
  });

  return app;
}
