<?php
/**
 * ANCP Core - Media + media links data access.
 *
 * The media table holds media objects (WP attachment or external URL) and
 * medialinks attaches them polymorphically (person/family/event/...). During
 * the profile_images transition (spec-00 6.3, spec-01 item 4) these tables
 * mirror profile_images: person profile photos are copied forward here and
 * dual-written on every profile_images change; the display reads the defphoto
 * medialink first and falls back to profile_images. profile_images stays the
 * source of truth for one release cycle and is retired later.
 *
 * Dual-write rules (0.9.0, now that first-class media exists alongside the
 * mirror):
 *  - Mirrored rows are identified by their 'PI' mediaID prefix. The mirror
 *    machinery (sync_person / delete_for_person / backfill) touches ONLY
 *    those rows; media created through the CRUD API below is never rebuilt
 *    or deleted by a profile_images change.
 *  - profile_images stays the source of truth for its own mirrored rows
 *    only. The API writes media/medialinks directly and never writes back
 *    to profile_images.
 *  - defphoto: a deliberate API choice wins. sync_person marks its first
 *    mirrored image defphoto only when the person has no non-PI defphoto
 *    link; set_defphoto() clears every other defphoto link on the target,
 *    mirrored ones included.
 *
 * @package AncestorPress\Core
 * @since   0.3.0
 */

namespace AncestorPress\Core;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Media / media-links CRUD (person profile photos during transition).
 *
 * @since 0.3.0
 */
class Media {


	/** @return string Media table name. */
	public static function media_table() {
		return Tables::media();
	}

	/** @return string Media-links table name. */
	public static function links_table() {
		return Tables::media_links();
	}

	/**
	 * Rebuild a person's profile-photo media + links from profile_images.
	 *
	 * Delete-and-recreate keeps it simple and always correct; called after any
	 * profile_images change for the person (dual-write) and by the backfill.
	 * Touches ONLY the PI-mirrored rows (0.9.0): media created through the
	 * CRUD API survives every profile_images change, and an API-set defphoto
	 * is never overridden by the mirror.
	 *
	 * @param  string $gedcom   Tree identifier.
	 * @param  string $personID GEDCOM person id.
	 * @return void
	 */
	public static function sync_person( $gedcom, $personID ) {
		global $wpdb;
		if ( '' === (string) $gedcom || '' === (string) $personID ) {
			return;
		}
		self::delete_for_person( $gedcom, $personID );

		// An API-created (non-mirrored) defphoto is a deliberate owner choice:
		// the mirror must not steal it back for profile_images.
		$links = self::links_table();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
		$has_api_defphoto = (int) $wpdb->get_var( $wpdb->prepare(
			"SELECT COUNT(*) FROM %i
			 WHERE gedcom = %s AND linktype = 'person' AND persfamID = %s AND defphoto = 1 AND mediaID NOT LIKE %s", $links,
			$gedcom, $personID, $wpdb->esc_like( 'PI' ) . '%'
		) );

		$imgs  = Profile_Images_Table::get_all_for_person( $gedcom, $personID );
		$first = ( 0 === $has_api_defphoto );
		$now   = Tables::now();
		foreach ( $imgs as $img ) {
			$media_id = 'PI' . (int) $img->id;
			$user_id  = (int) ( $img->created_by_user_id ?? 0 );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
			$wpdb->insert( self::media_table(), array(
				'gedcom'             => $gedcom,
				'mediaID'            => $media_id,
				'mediatype'          => 'photo',
				'external_url'       => $img->url,
				'thumbnail'          => $img->file,
				'description'        => $img->caption,
				'private'            => 0,
				'created_by_user_id' => $user_id,
				'created_at'         => $now,
				'updated_at'         => $now,
			) );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
			$wpdb->insert( self::links_table(), array(
				'gedcom'             => $gedcom,
				'mediaID'            => $media_id,
				'linktype'           => 'person',
				'persfamID'          => $personID,
				'defphoto'           => $first ? 1 : 0,
				'ordernum'           => (int) $img->sort_order,
				'created_by_user_id' => $user_id,
				'created_at'         => $now,
				'updated_at'         => $now,
			) );
			$first = false;
		}
	}

	/**
	 * The display photo URL for a person: the defphoto medialink's media,
	 * falling back to profile_images during the transition.
	 *
	 * @param  string $gedcom   Tree identifier.
	 * @param  string $personID GEDCOM person id.
	 * @return string           Image URL or ''.
	 */
	public static function get_display_photo_url( $gedcom, $personID ) {
		global $wpdb;
		$links = self::links_table();
		$media = self::media_table();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own tables; names cannot be placeholders.
		$row = $wpdb->get_row( $wpdb->prepare(
			"SELECT m.external_url, m.attachment_id
			 FROM %i l JOIN %i m ON m.gedcom = l.gedcom AND m.mediaID = l.mediaID
			 WHERE l.gedcom = %s AND l.linktype = 'person' AND l.persfamID = %s AND l.defphoto = 1
			 ORDER BY l.ordernum ASC, l.id ASC LIMIT 1", $links, $media,
			$gedcom, $personID
		) );
		if ( $row ) {
			if ( ! empty( $row->attachment_id ) ) {
				$url = wp_get_attachment_url( (int) $row->attachment_id );
				if ( $url ) {
					return $url;
				}
			}
			if ( ! empty( $row->external_url ) ) {
				return $row->external_url;
			}
		}
		// Transition fallback.
		$pi = Profile_Images_Table::get_primary( $gedcom, $personID );
		return ( $pi && ! empty( $pi->url ) ) ? $pi->url : '';
	}

	/**
	 * Delete a person's PI-mirrored profile-photo media + links.
	 *
	 * Scoped to the 'PI' mediaID prefix (0.9.0): this is the mirror's own
	 * cleanup, and first-class media attached to the person must survive it.
	 *
	 * @param  string $gedcom   Tree identifier.
	 * @param  string $personID GEDCOM person id.
	 * @return void
	 */
	public static function delete_for_person( $gedcom, $personID ) {
		global $wpdb;
		$links   = self::links_table();
		$media   = self::media_table();
		$pi_like = $wpdb->esc_like( 'PI' ) . '%';
		// Delete the mirrored media rows referenced by this person's person-type links.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own tables.
		$wpdb->query( $wpdb->prepare(
			"DELETE m FROM %i m JOIN %i l ON l.gedcom = m.gedcom AND l.mediaID = m.mediaID
			 WHERE l.gedcom = %s AND l.linktype = 'person' AND l.persfamID = %s AND m.mediaID LIKE %s", $media, $links,
			$gedcom, $personID, $pi_like
		) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
		$wpdb->query( $wpdb->prepare(
			"DELETE FROM %i
			 WHERE gedcom = %s AND linktype = 'person' AND persfamID = %s AND mediaID LIKE %s", $links,
			$gedcom, $personID, $pi_like
		) );
	}

	/**
	 * Delete all media + links for a tree.
	 *
	 * @param  string $gedcom Tree identifier.
	 * @return void
	 */
	public static function delete_by_tree( $gedcom ) {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
		$wpdb->delete( self::media_table(), array( 'gedcom' => $gedcom ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
		$wpdb->delete( self::links_table(), array( 'gedcom' => $gedcom ) );
	}

	// ── CRUD API (0.9.0) ─────────────────────────────────────────────────────
	//
	// The real media + medialinks API (spec-00 5.5/5.6): suite plugins write
	// media exclusively through these methods, never hand-written SQL.

	/**
	 * Fetch a media row by tree + mediaID.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom  Tree identifier.
	 * @param  string $mediaID Media reference (e.g. M1, M-1, PI3).
	 * @return object|null
	 */
	public static function get_by_ref( $gedcom, $mediaID ) {
		global $wpdb;
		return $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE gedcom = %s AND mediaID = %s LIMIT 1', self::media_table(), $gedcom, $mediaID ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * All media rows for a tree, in insertion order.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom Tree identifier.
	 * @return array
	 */
	public static function get_by_tree( $gedcom ) {
		global $wpdb;
		return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE gedcom = %s ORDER BY id ASC', self::media_table(), $gedcom ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Count media rows in a tree.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom Tree identifier.
	 * @return int
	 */
	public static function count_by_tree( $gedcom ) {
		global $wpdb;
		return (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE gedcom = %s', self::media_table(), $gedcom ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Insert a media row. Mints the next manual mediaID (M-n) when none is
	 * supplied; a supplied mediaID (GEDCOM import xref) must be unused.
	 *
	 * @since  0.9.0
	 * @param  array $data Field => value pairs (gedcom required).
	 * @return int|false   New row ID or false.
	 */
	public static function insert_media( $data ) {
		global $wpdb;
		$gedcom = isset( $data['gedcom'] ) ? (string) $data['gedcom'] : '';
		if ( '' === $gedcom ) {
			return false;
		}
		if ( empty( $data['mediaID'] ) ) {
			$data['mediaID'] = self::next_manual_id( $gedcom );
		} elseif ( self::get_by_ref( $gedcom, (string) $data['mediaID'] ) ) {
			return false;
		}
		$now  = Tables::now();
		$data = array_merge(
			array(
				'mediatype'          => 'photo',
				'created_by_user_id' => get_current_user_id(),
				'created_at'         => $now,
				'updated_at'         => $now,
			),
			$data
		);
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		$result = $wpdb->insert( self::media_table(), $data );
		return $result ? (int) $wpdb->insert_id : false;
	}

	/**
	 * Update a media row by row ID.
	 *
	 * @since  0.9.0
	 * @param  int   $id   Row ID.
	 * @param  array $data Field => value pairs (gedcom/mediaID changes ignored).
	 * @return int|false
	 */
	public static function update_media( $id, $data ) {
		global $wpdb;
		unset( $data['gedcom'], $data['mediaID'] );
		$data['updated_at'] = Tables::now();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		return $wpdb->update( self::media_table(), $data, array( 'id' => absint( $id ) ) );
	}

	/**
	 * Delete a media row AND all its links (a link without its media is
	 * meaningless). WP attachments are never deleted here - the media row is
	 * genealogy metadata around the attachment, not its owner.
	 *
	 * @since  0.9.0
	 * @param  int $id Row ID.
	 * @return int|false
	 */
	public static function delete_media_cascade( $id ) {
		global $wpdb;
		$id  = absint( $id );
		$row = $wpdb->get_row( $wpdb->prepare( 'SELECT gedcom, mediaID FROM %i WHERE id = %d', self::media_table(), $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		if ( ! $row ) {
			return false;
		}
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		$wpdb->delete( self::links_table(), array( 'gedcom' => $row->gedcom, 'mediaID' => $row->mediaID ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		return $wpdb->delete( self::media_table(), array( 'id' => $id ) );
	}

	/**
	 * Next manual media ID (M-1, M-2... - GEDCOM imports keep their own xrefs).
	 *
	 * @since  0.9.0
	 * @param  string $gedcom Tree identifier.
	 * @return string
	 */
	public static function next_manual_id( $gedcom ) {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- identifier via %i; LIKE prefix bound as %s.
		$rows = $wpdb->get_col( $wpdb->prepare( 'SELECT mediaID FROM %i WHERE gedcom = %s AND mediaID LIKE %s', self::media_table(), $gedcom, $wpdb->esc_like( 'M-' ) . '%' ) );
		$max  = 0;
		foreach ( $rows as $mid ) {
			$n = (int) substr( $mid, 2 );
			if ( $n > $max ) {
				$max = $n;
			}
		}
		return 'M-' . ( $max + 1 );
	}

	/**
	 * All links for one media item.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom  Tree identifier.
	 * @param  string $mediaID Media reference.
	 * @return array           Link rows.
	 */
	public static function links_for_media( $gedcom, $mediaID ) {
		global $wpdb;
		return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE gedcom = %s AND mediaID = %s ORDER BY linktype ASC, persfamID ASC, id ASC', self::links_table(), $gedcom, $mediaID ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Media attached to one target (person/family/...), display-ordered, with
	 * the link fields merged in. No privacy filtering here - the core API is
	 * an admin-level surface; display plugins apply the privacy floor in
	 * their own read layer.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom    Tree identifier.
	 * @param  string $linktype  person|family|event|source|citation|place.
	 * @param  string $persfamID Target reference.
	 * @return array             Media rows (+ link_id, defphoto, ordernum, eventID).
	 */
	public static function get_for_target( $gedcom, $linktype, $persfamID ) {
		global $wpdb;
		$links = self::links_table();
		$media = self::media_table();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own tables.
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT m.*, l.id AS link_id, l.defphoto, l.ordernum, l.eventID
			 FROM %i l JOIN %i m ON m.gedcom = l.gedcom AND m.mediaID = l.mediaID
			 WHERE l.gedcom = %s AND l.linktype = %s AND l.persfamID = %s
			 ORDER BY l.defphoto DESC, l.ordernum ASC, l.id ASC", $links, $media,
			$gedcom, $linktype, $persfamID
		) );
		return is_array( $rows ) ? $rows : array();
	}

	/**
	 * Link a media item to a target. Idempotent: re-linking the same media to
	 * the same target (same linktype + eventID) returns the existing link ID.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom    Tree identifier.
	 * @param  string $mediaID   Media reference (must exist).
	 * @param  string $linktype  person|family|event|source|citation|place.
	 * @param  string $persfamID Target reference.
	 * @param  array  $args      eventID (string), defphoto (bool), ordernum (int).
	 * @return int|false         Link row ID or false.
	 */
	public static function link( $gedcom, $mediaID, $linktype, $persfamID, $args = array() ) {
		global $wpdb;
		if ( '' === (string) $gedcom || '' === (string) $persfamID || ! self::get_by_ref( $gedcom, $mediaID ) ) {
			return false;
		}
		$event_id = isset( $args['eventID'] ) ? (string) $args['eventID'] : '';
		$defphoto = ! empty( $args['defphoto'] );
		$links    = self::links_table();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
		$existing = $wpdb->get_row( $wpdb->prepare(
			"SELECT id FROM %i
			 WHERE gedcom = %s AND mediaID = %s AND linktype = %s AND persfamID = %s AND COALESCE(eventID, '') = %s
			 LIMIT 1", $links,
			$gedcom, $mediaID, $linktype, $persfamID, $event_id
		) );
		if ( $existing ) {
			if ( $defphoto ) {
				self::set_defphoto( $gedcom, $linktype, $persfamID, $mediaID );
			}
			return (int) $existing->id;
		}

		if ( $defphoto ) {
			self::clear_defphoto( $gedcom, $linktype, $persfamID );
		}
		$now = Tables::now();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		$result = $wpdb->insert( $links, array(
			'gedcom'             => $gedcom,
			'mediaID'            => $mediaID,
			'linktype'           => $linktype,
			'persfamID'          => $persfamID,
			'eventID'            => '' !== $event_id ? $event_id : null,
			'defphoto'           => $defphoto ? 1 : 0,
			'ordernum'           => isset( $args['ordernum'] ) ? (int) $args['ordernum'] : 0,
			'created_by_user_id' => get_current_user_id(),
			'created_at'         => $now,
			'updated_at'         => $now,
		) );
		return $result ? (int) $wpdb->insert_id : false;
	}

	/**
	 * Remove one media link by link row ID. The media row itself is kept
	 * (it may be linked elsewhere); no defphoto auto-promotion happens.
	 *
	 * @since  0.9.0
	 * @param  int $link_id Link row ID.
	 * @return int|false
	 */
	public static function unlink( $link_id, $gedcom = '' ) {
		global $wpdb;
		// Scoped form (0.23.0): with $gedcom supplied the row only deletes
		// when it belongs to that tree - request-supplied link IDs cannot
		// reach across trees. Unscoped form kept for internal cascades.
		$where = array( 'id' => absint( $link_id ) );
		if ( '' !== (string) $gedcom ) {
			$where['gedcom'] = (string) $gedcom;
		}
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		return $wpdb->delete( self::links_table(), $where );
	}

	/**
	 * Make one media item the target's default/profile photo: every other
	 * defphoto flag on the target (mirrored PI links included) is cleared.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom    Tree identifier.
	 * @param  string $linktype  person|family.
	 * @param  string $persfamID Target reference.
	 * @param  string $mediaID   Media reference (its link must exist).
	 * @return bool
	 */
	public static function set_defphoto( $gedcom, $linktype, $persfamID, $mediaID ) {
		global $wpdb;
		$links = self::links_table();
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table.
		$link = $wpdb->get_row( $wpdb->prepare(
			"SELECT id FROM %i WHERE gedcom = %s AND linktype = %s AND persfamID = %s AND mediaID = %s LIMIT 1", $links,
			$gedcom, $linktype, $persfamID, $mediaID
		) );
		if ( ! $link ) {
			return false;
		}
		self::clear_defphoto( $gedcom, $linktype, $persfamID );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		$wpdb->update( $links, array( 'defphoto' => 1, 'updated_at' => Tables::now() ), array( 'id' => (int) $link->id ) );
		return true;
	}

	/**
	 * Clear every defphoto flag on a target.
	 *
	 * @since  0.9.0
	 * @param  string $gedcom    Tree identifier.
	 * @param  string $linktype  Link type.
	 * @param  string $persfamID Target reference.
	 * @return void
	 */
	public static function clear_defphoto( $gedcom, $linktype, $persfamID ) {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table write.
		$wpdb->update(
			self::links_table(),
			array( 'defphoto' => 0, 'updated_at' => Tables::now() ),
			array( 'gedcom' => $gedcom, 'linktype' => $linktype, 'persfamID' => $persfamID, 'defphoto' => 1 )
		);
	}

	/**
	 * One-time, idempotent copy-forward of profile_images into media +
	 * medialinks. Only rows not already present are inserted. The lowest
	 * sort_order image per person becomes the defphoto.
	 *
	 * @return void
	 */
	public static function backfill_from_profile_images() {
		global $wpdb;
		$pi    = Tables::profile_images();
		$media = self::media_table();
		$links = self::links_table();

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own tables; names cannot be placeholders.
		$wpdb->query(
			$wpdb->prepare( "INSERT INTO %i (gedcom, mediaID, mediatype, external_url, thumbnail, description, private, created_by_user_id, created_at, updated_at)
			 SELECT p.gedcom, CONCAT('PI', p.id), 'photo', p.url, p.file, p.caption, 0, p.created_by_user_id, p.created_at, p.updated_at
			 FROM %i p
			 WHERE NOT EXISTS ( SELECT 1 FROM %i m WHERE m.gedcom = p.gedcom AND m.mediaID = CONCAT('PI', p.id) )", $media, $pi, $media )
		);
		$wpdb->query(
			$wpdb->prepare( "INSERT INTO %i (gedcom, mediaID, linktype, persfamID, defphoto, ordernum, created_by_user_id, created_at, updated_at)
			 SELECT p.gedcom, CONCAT('PI', p.id), 'person', p.personID,
				CASE WHEN p.id = ( SELECT p2.id FROM %i p2 WHERE p2.gedcom = p.gedcom AND p2.personID = p.personID ORDER BY p2.sort_order ASC, p2.id ASC LIMIT 1 ) THEN 1 ELSE 0 END,
				p.sort_order, p.created_by_user_id, p.created_at, p.updated_at
			 FROM %i p
			 WHERE NOT EXISTS ( SELECT 1 FROM %i l WHERE l.gedcom = p.gedcom AND l.mediaID = CONCAT('PI', p.id) AND l.linktype = 'person' )", $links, $pi, $pi, $links )
		);
		// A deliberate API-set defphoto wins (0.9.0): drop the mirrored
		// defphoto flag wherever the person already has a non-PI one.
		$pi_like = $wpdb->esc_like( 'PI' ) . '%';
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE %i pi
				 JOIN %i api ON api.gedcom = pi.gedcom AND api.linktype = 'person' AND api.persfamID = pi.persfamID
					AND api.defphoto = 1 AND api.mediaID NOT LIKE %s
				 SET pi.defphoto = 0
				 WHERE pi.linktype = 'person' AND pi.defphoto = 1 AND pi.mediaID LIKE %s",
				$links,
				$links,
				$pi_like,
				$pi_like
			)
		);
		// phpcs:enable
	}
}
