/**
 * PhotoPrism API response shapes — only the fields the Svelte client uses.
 * Keep this surface narrow; extend as the UI grows.
 */

export type PpRole = 'admin' | 'user' | 'contributor' | 'guest' | 'visitor' | string;

export interface PpUser {
	UID: string;
	Name: string;
	DisplayName?: string;
	Email?: string;
	Role: PpRole;
}

export interface PpClientConfig {
	mode: 'public' | 'user';
	name: string;
	edition: string;
	version: string;
	siteUrl: string;
	siteTitle: string;
	previewToken: string;
	downloadToken: string;
	flags?: string;
	/**
	 * Per-instance hash that PhotoPrism uses to namespace its own
	 * `localStorage` entries (e.g. `pp:<namespace>:session.token`). The
	 * OIDC redirect HTML drops the issued session under this prefix; we
	 * read it back to adopt the SSO identity into the SPA store.
	 */
	storageNamespace?: string;
	/**
	 * Precomputed library counters. PhotoPrism updates these incrementally
	 * on every mutation, so they're cheap to read and accurate without a
	 * separate aggregate query. `all` already nets out review/hidden, which
	 * is what the timeline shows — prefer it over `photos + videos`.
	 */
	count?: {
		all?: number;
		photos?: number;
		videos?: number;
		live?: number;
		animated?: number;
		audio?: number;
		documents?: number;
		archived?: number;
		hidden?: number;
		favorites?: number;
		review?: number;
		private?: number;
		albums?: number;
		moments?: number;
		months?: number;
		states?: number;
		folders?: number;
		files?: number;
		people?: number;
		places?: number;
		labels?: number;
		cameras?: number;
		lenses?: number;
		countries?: number;
	};
	/**
	 * Optional extensions block. PhotoPrism reports OIDC availability and
	 * the per-provider login URI here so the SPA can render a "Sign in
	 * with <provider>" button. Empty (or `enabled:false`) when OIDC isn't
	 * configured.
	 */
	ext?: {
		oidc?: {
			enabled: boolean;
			provider?: string;
			loginUri?: string;
			icon?: string;
			register?: boolean;
			redirect?: boolean;
		};
	};
}

export interface PpSessionResponse {
	id: string;
	access_token: string;
	user: PpUser;
	config: PpClientConfig;
}

export interface PpPhoto {
	UID: string;
	/**
	 * `Hash` is only present on the list endpoint (`/photos?...`). The
	 * single-photo endpoint (`/photos/:uid`) returns the file hash nested
	 * under `Files[i].Hash` — use `primaryFile(photo).Hash` instead of
	 * reading this field unconditionally.
	 */
	Hash?: string;
	/** Auto-derived display name (no extension). */
	Name?: string;
	/** Originals-relative folder this photo lives in. Empty string for
	 *  photos directly under the originals root; otherwise the folder
	 *  path (e.g. `2024/lyon`). Populated on list responses. */
	Path?: string;
	/** PhotoPrism filename + extension, populated on list responses. */
	FileName?: string;
	/** User-editable original/preferred name. Persisted in DB + sidecar. */
	OriginalName?: string;
	Title?: string;
	TitleSrc?: string;
	Description?: string;
	Caption?: string;
	CaptionSrc?: string;
	TakenAt?: string;
	TakenAtLocal?: string;
	TakenSrc?: string;
	Year?: number;
	Month?: number;
	Day?: number;
	/** Top-level Width/Height appear on list responses but not on detail. */
	Width?: number;
	Height?: number;
	Rating?: number;
	Color?: string | number;
	Favorite?: boolean;
	Private?: boolean;
	Archived?: boolean;
	Files?: PpFile[];
	Lat?: number;
	Lng?: number;
	Altitude?: number;
	Country?: string;
	CountrySrc?: string;
	TimeZone?: string;
	Iso?: number;
	FNumber?: number;
	FocalLength?: number;
	Exposure?: string;
	Quality?: number;
	Type?: string;
	Camera?: PpCamera;
	CameraID?: number;
	CameraSrc?: string;
	Lens?: PpLens;
	LensID?: number;
	Place?: PpPlace;
	PlaceID?: string;
	PlaceSrc?: string;
	Details?: PpDetails;
	Labels?: PpPhotoLabel[];
	IndexedAt?: string;
	EditedAt?: string;
	UpdatedAt?: string;
	CreatedAt?: string;
}

export interface PpCamera {
	ID?: number;
	Slug?: string;
	Name?: string;
	Make?: string;
	Model?: string;
}

export interface PpLens {
	ID?: number;
	Slug?: string;
	Name?: string;
	Make?: string;
	Model?: string;
	Type?: string;
}

export interface PpPlace {
	ID?: string;
	Label?: string;
	PlaceLabel?: string;
	City?: string;
	State?: string;
	Country?: string;
}

export interface PpDetails {
	PhotoID?: number;
	Keywords?: string;
	KeywordsSrc?: string;
	Notes?: string;
	NotesSrc?: string;
	Subject?: string;
	SubjectSrc?: string;
	Artist?: string;
	ArtistSrc?: string;
	Copyright?: string;
	CopyrightSrc?: string;
	License?: string;
	LicenseSrc?: string;
	Software?: string;
	SoftwareSrc?: string;
}

export interface PpPhotoLabel {
	UID?: string;
	Source?: string;
	Priority?: number;
	Uncertainty?: number;
	Label?: { Slug: string; Name: string; Favorite?: boolean };
}

/**
 * Return the photo's primary file (the one with `Primary: true`) or the
 * first file if no primary marker is set. Falls back to a synthetic entry
 * that surfaces the top-level Hash so list-shape photos still resolve.
 */
export function primaryFile(p: PpPhoto): PpFile {
	const files = p.Files ?? [];
	const primary = files.find((f) => f.Primary) ?? files[0];
	if (primary) return primary;
	return {
		UID: p.UID,
		Hash: p.Hash ?? '',
		Name: p.FileName ?? p.Name ?? '',
		Root: '/',
		Primary: true,
		Width: p.Width,
		Height: p.Height
	};
}

export function isVideo(p: PpPhoto): boolean {
	return p.Type === 'video';
}

/** Return the Files[] entry that carries the actual video stream. Falls back
 *  to primaryFile() if no video MediaType is present (shouldn't happen for
 *  Type === 'video' but keeps the call site total). */
export function videoFile(p: PpPhoto): PpFile {
	const files = p.Files ?? [];
	const v = files.find((f) => f.MediaType?.startsWith('video/'));
	return v ?? primaryFile(p);
}

export interface PpFile {
	UID: string;
	Hash: string;
	Name: string;
	Root: string;
	Primary?: boolean;
	Stack?: number;
	Width?: number;
	Height?: number;
	Size?: number;
	FileType?: string;
	MediaType?: string;
}

export type PpThumbSize =
	| 'tile_50'
	| 'tile_100'
	| 'tile_224'
	| 'tile_500'
	| 'fit_720'
	| 'fit_1280'
	| 'fit_1920'
	| 'fit_2048'
	| 'fit_2560'
	| 'fit_3840'
	| 'fit_4096'
	| 'fit_7680';
