/* ---------------------------------------------------------------------------
 * Invitations resource.
 *
 * Create surfaces the magic link via ctx.result.invite-link → the route
 * handler propagates it as ?invite_url=... and the edit panel renders a
 * copy-to-clipboard block. The token itself is never stored — only its
 * hash — so the link is shown exactly once.
 *
 * "Revoke" is implemented as an action (sets expires_at = now()), not as
 * ops.delete, because the row stays in the table for audit history.
 * ------------------------------------------------------------------------- */

import {
  createInvite,
  getAllInvites,
  getInviteById,
  revokeInvite,
  type Invite,
  type Role,
} from '../../lib/db';
import { generateInviteToken, inviteExpiresAt } from '../../lib/auth';
import { relativeTime } from '../../lib/format';
import { fmtDateTime } from '../../lib/markdown';
import type { Resource } from '../resource-types';

type InviteRow = Invite & { creator_name: string | null };

function deriveStatus(item: InviteRow): 'pending' | 'accepted' | 'expired' {
  if (item.used_at) return 'accepted';
  if (new Date(item.expires_at).getTime() < Date.now()) return 'expired';
  return 'pending';
}

export const invitationsResource: Resource<InviteRow> = {
  key: 'invitations',
  label: 'Invitations',
  pluralLabel: 'Invitations',
  singularLabel: 'Invitation',
  groupKey: 'council',
  description: 'Magic links sent to new pilots and council members. Tokens are shown once on create.',

  list: {
    queryFn: () => getAllInvites(),
    columns: [
      {
        key: 'email',
        label: 'Email',
        primary: true,
        width: '2fr',
        render: (item) => ({
          title: item.email,
          subtitle: `${item.name} · ${item.organisation || '—'}`,
        }),
      },
      {
        key: 'role',
        label: 'Role',
        kind: 'pill',
        width: '110px',
        pillVariants: {
          pilot: { label: 'Pilot', class: 'pill-pilot' },
          cab: { label: 'Council', class: 'pill-cab' },
          fenja: { label: 'Fenja team', class: 'pill-fenja' },
        },
      },
      {
        key: 'creator_name',
        label: 'Invited by',
        width: '140px',
        render: (item) => ({ title: item.creator_name || '—' }),
      },
      {
        key: 'created_at',
        label: 'Created',
        kind: 'relative-date',
        width: '110px',
      },
      {
        key: 'status',
        label: 'Status',
        kind: 'pill',
        width: '110px',
        value: (item) => deriveStatus(item),
        pillVariants: {
          pending: { label: 'Pending', class: 'pill-pending' },
          accepted: { label: 'Accepted', class: 'pill-accepted' },
          expired: { label: 'Expired', class: 'pill-expired' },
        },
      },
    ],
    filters: [
      { key: 'all', label: 'All', predicate: () => true, isDefault: true },
      { key: 'pending', label: 'Pending', predicate: (i) => deriveStatus(i) === 'pending' },
      { key: 'accepted', label: 'Accepted', predicate: (i) => deriveStatus(i) === 'accepted' },
      { key: 'expired', label: 'Expired', predicate: (i) => deriveStatus(i) === 'expired' },
    ],
    search: {
      placeholder: 'Search by email or name…',
      fields: ['email', 'name', 'organisation'],
    },
    defaultSort: { key: 'created_at', direction: 'desc' },
    pageSize: 50,
  },

  form: {
    fields: [
      {
        key: 'email',
        label: 'Email',
        kind: 'text',
        required: true,
        maxLength: 240,
        pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
        helperText: 'Where the magic link will land. Cannot be changed later.',
      },
      { key: 'name', label: 'Name', kind: 'text', required: true, maxLength: 120 },
      { key: 'organisation', label: 'Organisation', kind: 'text', maxLength: 200 },
      {
        key: 'role',
        label: 'Role',
        kind: 'select',
        required: true,
        options: [
          { value: 'pilot', label: 'Pilot' },
          { value: 'cab', label: 'Council' },
        ],
        defaultValue: 'pilot',
        helperText: 'Council invites allocate a member number on signup.',
      },
    ],
  },

  // Existing invites are immutable — the panel renders the summary +
  // Revoke action via the review-mode pathway. Create still uses the form.
  summary: (item) => {
    const status = deriveStatus(item);
    return [
      { label: 'Email', value: item.email },
      { label: 'Name', value: item.name },
      { label: 'Organisation', value: item.organisation || '—' },
      {
        label: 'Role',
        value: item.role === 'cab' ? 'Council' : item.role === 'pilot' ? 'Pilot' : 'Fenja team',
      },
      { label: 'Status', value: status === 'pending' ? 'Pending' : status === 'accepted' ? 'Accepted' : 'Expired' },
      { label: 'Invited by', value: item.creator_name ?? '—' },
      { label: 'Created', value: relativeTime(item.created_at) },
      { label: 'Expires', value: fmtDateTime(item.expires_at) },
    ];
  },

  ops: {
    getById: (id) => getInviteById(id),

    create: (data, ctx) => {
      const { token, tokenHash } = generateInviteToken();
      const id = createInvite({
        token_hash: tokenHash,
        email: String(data.email).trim().toLowerCase(),
        name: String(data.name).trim(),
        organisation: ((data.organisation as string) ?? '').trim(),
        role: data.role as Role,
        expires_at: inviteExpiresAt(),
        created_by_user_id: ctx.user.id,
      });

      // Surface the one-shot magic link via the result mechanism — the route
      // handler propagates it as ?invite_url= and the panel renders a copy
      // block on the next page load. ctx.origin comes from Astro.url.origin
      // so the link is always absolute and clickable.
      ctx.result = {
        kind: 'invite-link',
        url: `${ctx.origin}/invite?t=${token}`,
      };
      return id;
    },
  },

  actions: [
    {
      key: 'revoke',
      label: 'Revoke',
      visibleWhen: (item) => deriveStatus(item) === 'pending',
      destructive: true,
      confirmText:
        'Revoke this invitation? The magic link will stop working immediately.',
      handler: (id) => {
        revokeInvite(id);
      },
    },
  ],
};
