import {
  type SubscriptionTier,
  tierSatisfies,
} from '../../common/access/subscription';
import type { PostVariant } from './community.repository';

/**
 * Who may open what in Community & Research, as pure functions.
 *
 * Two independent gates, both must pass:
 *   - the channel's minimum tier (`min_tier`, NULL = open), and
 *   - the post's edition: a PRO edition needs the PRO tier, a BASIS edition is
 *     open to Basis and Pro.
 * Everything that sends content to a browser asks `isPostLocked` first; the
 * list additionally asks `isPostListed` whether a post belongs in it at all.
 */

const ALL_VARIANTS: readonly PostVariant[] = ['PRO', 'BASIS'];

/** The edition a post needs from the reader's tier. */
function variantRequires(variant: PostVariant): SubscriptionTier {
  return variant === 'PRO' ? 'PRO' : 'BASIS';
}

/** Editions a tier may open — used to filter counters and "mark all read". */
export function openableVariants(tier: SubscriptionTier): PostVariant[] {
  return ALL_VARIANTS.filter((variant) => tierSatisfies(tier, variantRequires(variant)));
}

export function isChannelLocked(tier: SubscriptionTier, channelMinTier: SubscriptionTier | null): boolean {
  return !tierSatisfies(tier, channelMinTier);
}

export function isPostLocked(
  tier: SubscriptionTier,
  channelMinTier: SubscriptionTier | null,
  variant: PostVariant,
): boolean {
  return isChannelLocked(tier, channelMinTier) || !tierSatisfies(tier, variantRequires(variant));
}

/**
 * Whether a post appears in the reader's list at all. A locked channel lists
 * its posts as teasers — the channel as a whole is what Pro offers. In an open
 * channel an edition above the reader's tier is left out entirely: a Basis
 * reader sees their Basis issues, not a Pro teaser beside each one. The channel
 * then carries one notice instead (`editionsWithheld` in the bootstrap).
 */
export function isPostListed(
  tier: SubscriptionTier,
  channelMinTier: SubscriptionTier | null,
  variant: PostVariant,
): boolean {
  return isChannelLocked(tier, channelMinTier) || tierSatisfies(tier, variantRequires(variant));
}

/**
 * The only thing a locked post ever leaves the server as. No body, no cover, no
 * counters, no author — a teaser. `channelId`, `status` and `legacyDate` are
 * included beyond `{id, title, excerpt, publishedAt, variant}` only because the
 * list needs to file the teaser under its channel and date.
 */
export interface LockedPost {
  id: string;
  channelId: string;
  status: 'DRAFT' | 'PUBLISHED';
  variant: PostVariant;
  title: string;
  excerpt: string | null;
  legacyDate: string | null;
  publishedAt: Date | null;
  locked: true;
}

export function toLockedPost(post: {
  id: string;
  channelId: string;
  status: 'DRAFT' | 'PUBLISHED';
  variant: PostVariant;
  title: string;
  excerpt: string | null;
  legacyDate: string | null;
  publishedAt: Date | null;
}): LockedPost {
  return {
    id: post.id,
    channelId: post.channelId,
    status: post.status,
    variant: post.variant,
    title: post.title,
    excerpt: post.excerpt,
    legacyDate: post.legacyDate,
    publishedAt: post.publishedAt,
    locked: true,
  };
}
