/**
 * Builds the public `SpaceGallery` response in one place (PLANNING.md §5, §6d).
 * Every URL is absolutised here, `updatedAt` is `.toISOString()`d here, and in
 * dev/test the finished payload is parsed back through the frontend's own zod
 * schema (src/types/contract.ts) so drift fails loudly on our side.
 */
import { AppError } from '../../lib/AppError';
import { env } from '../../env';
import { fromSqlUtc } from '../../lib/dates';
import { spaceGallerySchema, type GalleryImage, type SpaceGallery } from '../../types/contract';
import { assetUrl } from './asset-url';
import { listReadyImages, listVariants, type MediaVariantRow } from './media.repository';

const SLUGS = ['genesis', 'hive'] as const;
type Slug = (typeof SLUGS)[number];

function isSlug(value: string): value is Slug {
  return (SLUGS as readonly string[]).includes(value);
}

export async function getSpaceGallery(slug: string, categorySlug?: string): Promise<SpaceGallery> {
  if (!isSlug(slug)) throw AppError.notFound(`Unknown space "${slug}"`);

  const rows = await listReadyImages(slug, categorySlug);
  const variants = await listVariants(rows.map((r) => r.id));

  const byImage = new Map<string, MediaVariantRow[]>();
  for (const v of variants) {
    const list = byImage.get(v.image_id);
    if (list) list.push(v);
    else byImage.set(v.image_id, [v]);
  }

  const images: GalleryImage[] = rows.map((row) => {
    const vs = byImage.get(row.id) ?? [];
    const primary =
      vs.find((v) => v.format === 'jpeg' && v.is_primary) ?? vs.find((v) => v.format === 'jpeg');
    const pick = (format: 'avif' | 'webp') =>
      vs
        .filter((v) => v.format === format)
        .sort((a, b) => a.width - b.width)
        .map((v) => ({ url: assetUrl(v.path), width: v.width }));

    return {
      id: row.id,
      alt: row.alt,
      order: row.position,
      width: row.width,
      height: row.height,
      src: primary ? assetUrl(primary.path) : assetUrl(`${row.id}/original.jpg`),
      variants: { avif: pick('avif'), webp: pick('webp') },
      ...(row.caption ? { caption: row.caption } : {}),
    };
  });

  const updatedAt =
    rows.length > 0
      ? fromSqlUtc(
          rows.reduce((max, r) => (r.updated_at > max ? r.updated_at : max), rows[0].updated_at),
        ).toISOString()
      : new Date().toISOString();

  const payload: SpaceGallery = { slug, updatedAt, images };

  // Self-check against the consumer's parser (PLANNING.md §5/§12). Skipped for
  // the documented empty-gallery case — the frontend's schema is `.min(1)` (M2).
  if (env.NODE_ENV !== 'production' && images.length > 0) {
    spaceGallerySchema.parse(payload);
  }

  return payload;
}
