/**
 * Server-side helpers shared by the tag routes: portfolio-role lookup and the
 * write gate. Tags are portfolio-scoped; per ADR-0013 (Q11) any member with a
 * write-capable role manages and assigns them, read members are view-only.
 */
import { db } from "@/app/db/db";
import { portfolioUsers } from "@/app/db/schema/portfolio";
import { user } from "@/app/db/schema/users";
import { and, eq } from "drizzle-orm";

/** Roles allowed to create/edit/delete/assign tags (read is view-only). */
const WRITE_ROLES = new Set(["creator", "admin", "write"]);

export async function getPortfolioRole(
  portfolioId: bigint,
  email: string,
): Promise<string | null> {
  const rows = await db
    .select({ role: portfolioUsers.role })
    .from(portfolioUsers)
    .innerJoin(user, eq(user.id, portfolioUsers.userId))
    .where(
      and(eq(portfolioUsers.portfolioId, portfolioId), eq(user.email, email)),
    )
    .limit(1);
  return rows[0]?.role ?? null;
}

/** A member (any role) may read; only write-capable roles may mutate. */
export function canReadTags(role: string | null): boolean {
  return role != null;
}

export function canWriteTags(role: string | null): boolean {
  return role != null && WRITE_ROLES.has(role);
}

/**
 * True when `err` is a Postgres unique-violation (SQLSTATE 23505) — the
 * case-insensitive tag-name index racing a concurrent create/rename. node-postgres
 * surfaces the code on the thrown error (or its `.cause`). Everything else is a
 * real failure the caller must not mask as a duplicate-name 409.
 */
export function isUniqueViolation(err: unknown): boolean {
  const code = (err as { code?: unknown; cause?: { code?: unknown } })?.code
    ?? (err as { cause?: { code?: unknown } })?.cause?.code;
  return code === "23505";
}
