<?php
/**
 * AP Family Archive - Media management (admin).
 *
 * The media curation surface (spec-02 separate package, P1 build): upload to the WP
 * media library or reference an external URL, link/unlink to people and
 * families (event linkage optional), set the profile photo, edit caption /
 * as-recorded date / private flag / transcription, delete. ALL writes go
 * through the core media API (Media, core 0.9.0) - never hand-written
 * SQL. The tab itself is free (data management is free everywhere in the
 * suite); the public gallery surfaces belong to the separate package.
 *
 * @package    APFAAR_Family_Archive
 * @subpackage APFAAR_Family_Archive/admin
 * @since      0.1.0
 */

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

/**
 * Media tab: media list/add/edit/delete + link management.
 *
 * @since 0.1.0
 */
class APFAAR_Admin_Media {

	/**
	 * Media types the form offers (spec-00 5.5 conventions). Values are
	 * stored verbatim; imported GEDCOMs may carry other strings and those
	 * are preserved, not rewritten.
	 *
	 * @return array type => label.
	 */
	public static function types() {
		return array(
			'photo'       => __( 'Photograph', 'ap-family-archive' ),
			'document'    => __( 'Document', 'ap-family-archive' ),
			'certificate' => __( 'Certificate', 'ap-family-archive' ),
			'headstone'   => __( 'Headstone', 'ap-family-archive' ),
			'map'         => __( 'Map', 'ap-family-archive' ),
			'audio'       => __( 'Audio', 'ap-family-archive' ),
			'video'       => __( 'Video', 'ap-family-archive' ),
			'other'       => __( 'Other', 'ap-family-archive' ),
		);
	}

	/**
	 * Whether the booted core carries the media CRUD API (core 0.9.0).
	 *
	 * @return bool
	 */
	public static function available() {
		return class_exists( 'AncestorPress\Core\Media' ) && method_exists( 'AncestorPress\Core\Media', 'insert_media' );
	}

	/**
	 * Render the Media tab.
	 *
	 * @param  string $gedcom Working tree.
	 * @return void
	 */
	public static function render( $gedcom ) {
		if ( ! self::available() ) {
			echo '<div class="apfa-notice-amber">' . esc_html__( 'The data core on this site predates the media API (core 0.9.0). Update the suite plugins and reload.', 'ap-family-archive' ) . '</div>';
			return;
		}

		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only view routing.
		$action = isset( $_GET['apfaar_action'] ) ? sanitize_key( wp_unslash( $_GET['apfaar_action'] ) ) : '';
		$mid    = isset( $_GET['apfaar_mid'] ) ? sanitize_text_field( wp_unslash( $_GET['apfaar_mid'] ) ) : '';
		// phpcs:enable

		if ( 'media_add' === $action || ( 'media_edit' === $action && '' !== $mid ) ) {
			self::render_form( $gedcom, 'media_edit' === $action ? $mid : '' );
			return;
		}
		self::render_list( $gedcom );
	}

	/**
	 * URL of the Media tab with args.
	 *
	 * @param  string $gedcom Working tree.
	 * @param  array  $args   Extra query args.
	 * @return string
	 */
	public static function url( $gedcom, $args = array() ) {
		return add_query_arg(
			array_merge(
				array(
					'page' => 'apfaar',
					'tab'  => 'media',
					'tree' => rawurlencode( $gedcom ),
				),
				$args
			),
			admin_url( 'admin.php' )
		);
	}

	/**
	 * Small thumbnail HTML for a media row ('' when none resolvable).
	 *
	 * @param  object $row Media row.
	 * @return string
	 */
	private static function thumb_html( $row ) {
		$url = APFAAR_People::media_thumb_url( $row );
		if ( '' === $url ) {
			return '';
		}
		return '<img src="' . esc_url( $url ) . '" alt="" style="width:48px;height:48px;object-fit:cover;border-radius:3px" loading="lazy" />';
	}

	/**
	 * The media list.
	 *
	 * @param  string $gedcom Working tree.
	 * @return void
	 */
	private static function render_list( $gedcom ) {
		$rows  = \AncestorPress\Core\Media::get_by_tree( $gedcom );
		$types = self::types();

		echo '<div class="apfa-section-card apfa-table-card" style="padding:16px">';
		echo '<p class="description">' . esc_html__( 'Photographs, documents, headstones and other media for this tree. Upload a file (it goes into the WordPress media library) or reference one hosted elsewhere, then link it to the people and families it shows. The first linked photo becomes a record\'s profile photo unless you choose another. Items flagged private are never shown to public visitors.', 'ap-family-archive' ) . '</p>';
		echo '<p><a class="button button-primary" href="' . esc_url( self::url( $gedcom, array( 'apfaar_action' => 'media_add' ) ) ) . '">' . esc_html__( 'Add media', 'ap-family-archive' ) . '</a> ';
		/* translators: %s: number of media items. */
		echo '<span class="apfa-filter-count">' . esc_html( sprintf( __( '%s media items in this tree', 'ap-family-archive' ), number_format_i18n( is_array( $rows ) ? count( $rows ) : 0 ) ) ) . '</span></p>';

		if ( ! empty( $rows ) ) {
			echo '<table class="widefat striped"><thead><tr>'
				. '<th style="width:56px"></th>'
				. '<th>' . esc_html__( 'ID', 'ap-family-archive' ) . '</th>'
				. '<th>' . esc_html__( 'Type', 'ap-family-archive' ) . '</th>'
				. '<th>' . esc_html__( 'Caption', 'ap-family-archive' ) . '</th>'
				. '<th>' . esc_html__( 'Date', 'ap-family-archive' ) . '</th>'
				. '<th>' . esc_html__( 'Linked to', 'ap-family-archive' ) . '</th>'
				. '<th>' . esc_html__( 'Actions', 'ap-family-archive' ) . '</th>'
				. '</tr></thead><tbody>';
			foreach ( $rows as $row ) {
				$links   = \AncestorPress\Core\Media::links_for_media( $gedcom, $row->mediaID );
				$targets = array();
				foreach ( (array) $links as $l ) {
					$targets[] = $l->persfamID . ( $l->defphoto ? ' ' . __( '(profile)', 'ap-family-archive' ) : '' );
				}
				$delete_url = wp_nonce_url(
					admin_url( 'admin-post.php?action=apfaar_media_delete&tree=' . rawurlencode( $gedcom ) . '&apfaar_mid=' . rawurlencode( $row->mediaID ) ),
					'apfaar_media_delete_' . $row->mediaID
				);
				echo '<tr>'
					. '<td>' . wp_kses_post( self::thumb_html( $row ) ) . '</td>'
					. '<td><span class="apfa-gedcom-ref">' . esc_html( $row->mediaID ) . '</span></td>'
					. '<td>' . esc_html( isset( $types[ $row->mediatype ] ) ? $types[ $row->mediatype ] : (string) $row->mediatype ) . '</td>'
					. '<td>' . esc_html( (string) $row->description )
						. ( ! empty( $row->private ) ? ' <span class="apfa-badge-private">' . esc_html__( 'private', 'ap-family-archive' ) . '</span>' : '' ) . '</td>'
					. '<td>' . esc_html( (string) $row->mediadate ) . '</td>'
					. '<td>' . esc_html( $targets ? implode( ', ', $targets ) : __( 'not linked', 'ap-family-archive' ) ) . '</td>'
					. '<td><a class="apfa-action-edit" href="' . esc_url( self::url( $gedcom, array( 'apfaar_action' => 'media_edit', 'apfaar_mid' => $row->mediaID ) ) ) . '">' . esc_html__( 'Edit', 'ap-family-archive' ) . '</a> | '
						. '<a class="apfa-action-delete" href="' . esc_url( $delete_url ) . '" onclick="return confirm(\'' . esc_js( __( 'Delete this media item AND all its links? Files stay in the WordPress media library. This cannot be undone.', 'ap-family-archive' ) ) . '\')">' . esc_html__( 'Delete', 'ap-family-archive' ) . '</a></td>'
					. '</tr>';
			}
			echo '</tbody></table>';
		} else {
			echo '<p>' . esc_html__( 'No media yet. Add an item, or import a GEDCOM that carries media (OBJE) records.', 'ap-family-archive' ) . '</p>';
		}
		echo '</div>';
	}

	/**
	 * Add/edit media form (+ links panel on edit).
	 *
	 * @param  string $gedcom Working tree.
	 * @param  string $mid    Media to edit ('' = add).
	 * @return void
	 */
	private static function render_form( $gedcom, $mid ) {
		$row = '' !== $mid ? \AncestorPress\Core\Media::get_by_ref( $gedcom, $mid ) : null;
		if ( '' !== $mid && ! $row ) {
			echo '<div class="apfa-notice-amber">' . esc_html__( 'Media item not found in this tree.', 'ap-family-archive' ) . '</div>';
			return;
		}
		$val = function ( $field ) use ( $row ) {
			return $row ? (string) ( $row->{$field} ?? '' ) : '';
		};

		echo '<div class="apfa-section-card">';
		echo '<h2 class="apfa-options-section-title">'
			. ( $row
				? esc_html__( 'Edit media', 'ap-family-archive' ) . ' <span class="apfa-gedcom-ref">' . esc_html( $mid ) . '</span>'
				: esc_html__( 'Add media', 'ap-family-archive' ) )
			. '</h2>';

		echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" enctype="multipart/form-data">';
		wp_nonce_field( 'apfaar_media_save' );
		echo '<input type="hidden" name="action" value="apfaar_media_save" />';
		echo '<input type="hidden" name="tree" value="' . esc_attr( $gedcom ) . '" />';
		echo '<input type="hidden" name="apfaar_mid" value="' . esc_attr( $mid ) . '" />';

		// The file itself.
		if ( $row ) {
			$current = APFAAR_People::media_url( $row );
			echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'File', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">';
			if ( ! empty( $row->attachment_id ) ) {
				echo esc_html__( 'WordPress media library item', 'ap-family-archive' ) . ' #' . (int) $row->attachment_id;
				echo $current ? ' - <a href="' . esc_url( $current ) . '" target="_blank" rel="noopener">' . esc_html__( 'view', 'ap-family-archive' ) . '</a>' : '';
				echo '<br /><span class="description">' . esc_html__( 'To use a different file, add a new media item; the file itself is not replaceable here (links and captions would silently change meaning).', 'ap-family-archive' ) . '</span>';
			} else {
				echo '<input type="url" name="external_url" value="' . esc_attr( $val( 'external_url' ) ) . '" style="width:100%;max-width:480px" />';
				echo '<br /><span class="description">' . esc_html__( 'Web address of the file (hosted elsewhere). Change it if the file moved.', 'ap-family-archive' ) . '</span>';
			}
			echo '</span></div>';
		} else {
			echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'Upload file', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">'
				. '<input type="file" name="apfaar_media_file" />'
				. '<br /><span class="description">' . esc_html__( 'Goes into the WordPress media library (images, PDFs, audio, video - whatever your site allows). Leave empty if you use a web address below.', 'ap-family-archive' ) . '</span></span></div>';
			echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'or web address', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">'
				. '<input type="url" name="external_url" placeholder="https://" style="width:100%;max-width:480px" />'
				. '<br /><span class="description">' . esc_html__( 'For a file hosted elsewhere. One of the two is required - an upload wins if you provide both.', 'ap-family-archive' ) . '</span></span></div>';
		}

		// Type.
		echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'Type', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control"><select name="mediatype">';
		$current_type = '' !== $val( 'mediatype' ) ? $val( 'mediatype' ) : 'photo';
		$known        = self::types();
		if ( ! isset( $known[ $current_type ] ) ) {
			// Imported value outside our list: keep it selectable, never rewrite it.
			echo '<option value="' . esc_attr( $current_type ) . '" selected>' . esc_html( $current_type ) . '</option>';
		}
		foreach ( $known as $type => $label ) {
			echo '<option value="' . esc_attr( $type ) . '"' . selected( $current_type, $type, false ) . '>' . esc_html( $label ) . '</option>';
		}
		echo '</select><br /><span class="description">' . esc_html__( 'What the item is. "Headstone" powers the headstone display on person pages (Pro); "Document" and "Certificate" open in the document viewer (Pro).', 'ap-family-archive' ) . '</span></span></div>';

		// Caption, date, transcription, private.
		echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'Caption', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">'
			. '<input type="text" name="description" value="' . esc_attr( $val( 'description' ) ) . '" style="width:100%;max-width:480px" />'
			. '<br /><span class="description">' . esc_html__( 'Shown under the item wherever it appears.', 'ap-family-archive' ) . '</span></span></div>';

		echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'Date', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">'
			. '<input type="text" name="mediadate" value="' . esc_attr( $val( 'mediadate' ) ) . '" placeholder="' . esc_attr__( 'e.g. ABT 1880, 14 FEB 1868', 'ap-family-archive' ) . '" />'
			. '<br /><span class="description">' . esc_html__( 'When the item was made, exactly as recorded - approximate forms like ABT 1880 are kept verbatim and never rewritten; a sortable date is derived behind the scenes.', 'ap-family-archive' ) . '</span></span></div>';

		echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'Transcription', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">'
			. '<textarea name="transcription" rows="4" style="width:100%;max-width:480px">' . esc_textarea( $val( 'transcription' ) ) . '</textarea>'
			. '<br /><span class="description">' . esc_html__( 'The item\'s text, typed out (a will, a letter, a headstone inscription). The document viewer (Pro) shows it beside the image, and search can find it.', 'ap-family-archive' ) . '</span></span></div>';

		echo '<div class="apfa-field-row"><span class="apfa-field-row-label">' . esc_html__( 'Private', 'ap-family-archive' ) . '</span><span class="apfa-field-row-control">'
			. '<label><input type="checkbox" name="private" value="1"' . checked( '1', $val( 'private' ), false ) . ' /> '
			. esc_html__( 'Hide from public visitors. Only users allowed to see private records (site administrators, unless refined later) will see this item anywhere on the public site.', 'ap-family-archive' ) . '</label></span></div>';

		echo '<div class="apfa-save-bar">';
		submit_button( $row ? __( 'Save changes', 'ap-family-archive' ) : __( 'Add media', 'ap-family-archive' ), 'primary', 'submit', false );
		echo ' <a class="button button-secondary" href="' . esc_url( self::url( $gedcom ) ) . '">' . esc_html__( 'Back to list', 'ap-family-archive' ) . '</a>';
		echo '</div></form></div>';

		// Links panel (edit mode only - the item must exist before it can be linked).
		if ( $row ) {
			self::render_links_panel( $gedcom, $row );
		} else {
			echo '<p class="description">' . esc_html__( 'Save the item first - then you can link it to people and families.', 'ap-family-archive' ) . '</p>';
		}
	}

	/**
	 * The "Linked to" panel on the edit screen: existing links with actions +
	 * an add-link form.
	 *
	 * @param  string $gedcom Working tree.
	 * @param  object $row    Media row.
	 * @return void
	 */
	private static function render_links_panel( $gedcom, $row ) {
		$links = \AncestorPress\Core\Media::links_for_media( $gedcom, $row->mediaID );

		echo '<div class="apfa-section-card">';
		echo '<h2 class="apfa-options-section-title">' . esc_html__( 'Linked to', 'ap-family-archive' ) . '</h2>';
		echo '<p class="description">' . esc_html__( 'The people and families this item belongs to - it appears on each linked record\'s public page. "Make profile photo" puts it at the top of that record (one profile photo per record; choosing this one un-chooses any other).', 'ap-family-archive' ) . '</p>';

		if ( ! empty( $links ) ) {
			echo '<table class="widefat striped"><tbody>';
			foreach ( $links as $l ) {
				$name = '';
				if ( 'person' === $l->linktype ) {
					$p    = APFAAR_People::get_person_raw( $gedcom, $l->persfamID );
					$name = $p ? APFAAR_People::display_name( $p ) : __( '(record not found)', 'ap-family-archive' );
				} elseif ( 'family' === $l->linktype ) {
					$name = __( 'Family', 'ap-family-archive' );
				}
				$unlink_url = wp_nonce_url(
					admin_url( 'admin-post.php?action=apfaar_media_unlink&tree=' . rawurlencode( $gedcom ) . '&apfaar_mid=' . rawurlencode( $row->mediaID ) . '&link=' . (int) $l->id ),
					'apfaar_media_unlink_' . (int) $l->id
				);
				$def_url = wp_nonce_url(
					admin_url( 'admin-post.php?action=apfaar_media_defphoto&tree=' . rawurlencode( $gedcom ) . '&apfaar_mid=' . rawurlencode( $row->mediaID ) . '&linktype=' . rawurlencode( $l->linktype ) . '&ref=' . rawurlencode( $l->persfamID ) ),
					'apfaar_media_defphoto_' . $l->persfamID
				);
				echo '<tr><td>' . esc_html( $name ) . ' <span class="apfa-gedcom-ref">' . esc_html( $l->persfamID ) . '</span>'
					. ( ! empty( $l->eventID ) ? ' <span class="description">' . esc_html( sprintf( /* translators: %s: event id. */ __( 'for event %s', 'ap-family-archive' ), $l->eventID ) ) . '</span>' : '' )
					. ( $l->defphoto ? ' <strong>' . esc_html__( 'profile photo', 'ap-family-archive' ) . '</strong>' : '' )
					. '</td><td style="width:260px">'
					. ( ! $l->defphoto && in_array( $l->linktype, array( 'person', 'family' ), true )
						? '<a href="' . esc_url( $def_url ) . '">' . esc_html__( 'Make profile photo', 'ap-family-archive' ) . '</a> | '
						: '' )
					. '<a class="apfa-action-delete" href="' . esc_url( $unlink_url ) . '" onclick="return confirm(\'' . esc_js( __( 'Unlink from this record? The media item itself is kept.', 'ap-family-archive' ) ) . '\')">' . esc_html__( 'Unlink', 'ap-family-archive' ) . '</a>'
					. '</td></tr>';
			}
			echo '</tbody></table>';
		} else {
			echo '<p class="description">' . esc_html__( 'Not linked to any record yet - it will not appear anywhere on the public site until it is.', 'ap-family-archive' ) . '</p>';
		}

		echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="margin-top:10px">';
		wp_nonce_field( 'apfaar_media_link' );
		echo '<input type="hidden" name="action" value="apfaar_media_link" />';
		echo '<input type="hidden" name="tree" value="' . esc_attr( $gedcom ) . '" />';
		echo '<input type="hidden" name="apfaar_mid" value="' . esc_attr( $row->mediaID ) . '" />';
		echo '<label>' . esc_html__( 'Link to', 'ap-family-archive' ) . ' <select name="linktype">'
			. '<option value="person">' . esc_html__( 'Person', 'ap-family-archive' ) . '</option>'
			. '<option value="family">' . esc_html__( 'Family', 'ap-family-archive' ) . '</option>'
			. '</select></label> ';
		echo '<label>' . esc_html__( 'Record ID', 'ap-family-archive' ) . ' <input type="text" name="ref" placeholder="I123 / F12" required /></label> ';
		echo '<label>' . esc_html__( 'Event (optional)', 'ap-family-archive' ) . ' <input type="text" name="event" placeholder="E7" size="5" /></label> ';
		echo '<label><input type="checkbox" name="defphoto" value="1" /> ' . esc_html__( 'Make profile photo', 'ap-family-archive' ) . '</label> ';
		submit_button( __( 'Link', 'ap-family-archive' ), 'secondary', 'submit', false );
		echo '<p class="description">' . esc_html__( 'The record ID is the I-number (person) or F-number (family) shown on its edit screen and public page; it must exist in this tree. The optional event is the E-number shown next to an event on the record\'s edit screen - use it to say "this image shows that event" (e.g. a census page on the census event); it must belong to the same record.', 'ap-family-archive' ) . '</p>';
		echo '</form></div>';
	}

	// ── Handler bodies (called from APFAAR_Admin admin-post endpoints) ──────────

	/**
	 * Save (add or update) a media item. Uploads go to the WP media library.
	 *
	 * @param  string $gedcom Working tree.
	 * @return array          { mid: string, error: string|null }
	 */
	public static function save( $gedcom ) {
		// phpcs:disable WordPress.Security.NonceVerification.Missing -- caller (APFAAR_Admin) verified cap + nonce.
		$mid  = isset( $_POST['apfaar_mid'] ) ? sanitize_text_field( wp_unslash( $_POST['apfaar_mid'] ) ) : '';
		$date = isset( $_POST['mediadate'] ) ? sanitize_text_field( wp_unslash( $_POST['mediadate'] ) ) : '';
		$data = array(
			'mediatype'     => isset( $_POST['mediatype'] ) ? sanitize_text_field( wp_unslash( $_POST['mediatype'] ) ) : 'photo',
			'description'   => isset( $_POST['description'] ) ? sanitize_text_field( wp_unslash( $_POST['description'] ) ) : '',
			'mediadate'     => $date,
			'mediadatetr'   => \AncestorPress\Core\Tables::normalise_date( $date ),
			'transcription' => isset( $_POST['transcription'] ) ? sanitize_textarea_field( wp_unslash( $_POST['transcription'] ) ) : '',
			'private'       => empty( $_POST['private'] ) ? 0 : 1,
		);
		$external = isset( $_POST['external_url'] ) ? esc_url_raw( wp_unslash( $_POST['external_url'] ) ) : '';
		// phpcs:enable

		if ( '' !== $mid ) {
			$existing = \AncestorPress\Core\Media::get_by_ref( $gedcom, $mid );
			if ( ! $existing ) {
				return array( 'mid' => '', 'error' => __( 'Media item not found in this tree.', 'ap-family-archive' ) );
			}
			if ( empty( $existing->attachment_id ) ) {
				$data['external_url'] = $external;
			}
			\AncestorPress\Core\Media::update_media( (int) $existing->id, $data );
			return array( 'mid' => $mid, 'error' => null );
		}

		// New item: an upload wins over a URL; one of the two is required.
		$has_upload = ! empty( $_FILES['apfaar_media_file']['name'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		if ( $has_upload ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
			require_once ABSPATH . 'wp-admin/includes/image.php';
			require_once ABSPATH . 'wp-admin/includes/media.php';
			$attachment_id = media_handle_upload( 'apfaar_media_file', 0 );
			if ( is_wp_error( $attachment_id ) ) {
				return array( 'mid' => '', 'error' => $attachment_id->get_error_message() );
			}
			$data['attachment_id'] = (int) $attachment_id;
		} elseif ( '' !== $external ) {
			$data['external_url'] = $external;
		} else {
			return array( 'mid' => '', 'error' => __( 'Upload a file or give a web address - the item needs one of the two.', 'ap-family-archive' ) );
		}

		$data['gedcom'] = $gedcom;
		$row_id         = \AncestorPress\Core\Media::insert_media( $data );
		if ( ! $row_id ) {
			return array( 'mid' => '', 'error' => __( 'Could not save the media item.', 'ap-family-archive' ) );
		}
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- immediate read-back of the row just inserted through the core API.
		$new = $wpdb->get_row( $wpdb->prepare( 'SELECT mediaID FROM %i WHERE id = %d', \AncestorPress\Core\Media::media_table(), $row_id ) );
		return array( 'mid' => $new ? $new->mediaID : '', 'error' => null );
	}

	/**
	 * Delete a media item and its links (the WP attachment is kept).
	 *
	 * @param  string $gedcom Working tree.
	 * @param  string $mid    Media reference.
	 * @return void
	 */
	public static function delete( $gedcom, $mid ) {
		$existing = \AncestorPress\Core\Media::get_by_ref( $gedcom, $mid );
		if ( $existing ) {
			\AncestorPress\Core\Media::delete_media_cascade( (int) $existing->id );
		}
	}

	/**
	 * Link a media item to a person/family (handler body).
	 *
	 * @param  string $gedcom Working tree.
	 * @param  string $mid    Media reference.
	 * @return string|null    Error message or null on success.
	 */
	public static function add_link( $gedcom, $mid ) {
		// phpcs:disable WordPress.Security.NonceVerification.Missing -- caller (APFAAR_Admin) verified cap + nonce.
		$linktype = isset( $_POST['linktype'] ) && 'family' === $_POST['linktype'] ? 'family' : 'person';
		$ref      = isset( $_POST['ref'] ) ? sanitize_text_field( wp_unslash( $_POST['ref'] ) ) : '';
		$event    = isset( $_POST['event'] ) ? sanitize_text_field( wp_unslash( $_POST['event'] ) ) : '';
		$defphoto = ! empty( $_POST['defphoto'] );
		// phpcs:enable

		if ( '' === $ref ) {
			return __( 'Give the record ID to link to.', 'ap-family-archive' );
		}
		if ( 'person' === $linktype && ! \AncestorPress\Core\People_Table::get_by_ref( $gedcom, $ref ) ) {
			return __( 'No person with that ID exists in this tree - check the I-number on the People tab.', 'ap-family-archive' );
		}
		if ( 'family' === $linktype && ! \AncestorPress\Core\Families_Table::get_by_ref( $gedcom, $ref ) ) {
			return __( 'No family with that ID exists in this tree - check the F-number on the Families tab.', 'ap-family-archive' );
		}
		if ( '' !== $event ) {
			$valid = false;
			foreach ( APFAAR_People::events_for( $gedcom, $ref ) as $ev ) {
				if ( ! empty( $ev->eventID ) && (string) $ev->eventID === $event ) {
					$valid = true;
					break;
				}
			}
			if ( ! $valid ) {
				return __( 'That event does not belong to this record - link not added.', 'ap-family-archive' );
			}
		}

		$existing = \AncestorPress\Core\Media::get_for_target( $gedcom, $linktype, $ref );
		$has_def  = false;
		foreach ( $existing as $e ) {
			if ( ! empty( $e->defphoto ) ) {
				$has_def = true;
				break;
			}
		}
		// A record's first media becomes its profile photo automatically
		// (stated in the tab help text); a later explicit choice overrides.
		$result = \AncestorPress\Core\Media::link( $gedcom, $mid, $linktype, $ref, array(
			'eventID'  => $event,
			'defphoto' => $defphoto || ! $has_def,
			'ordernum' => count( $existing ),
		) );
		return $result ? null : __( 'Could not create the link.', 'ap-family-archive' );
	}
}
