<?php
/**
 * Plugin Name: SEO Meta
 * Description: SEO, Open Graph, Twitter and canonical meta for posts and pages (CLI + admin).
 * Version: 1.0
 */

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

/**
 * @return array<string, array{label:string, type:string, rows?:int}>
 */
function seo_meta_field_definitions(): array {
	return [
		'title'                => [
			'label' => 'SEO Title',
			'type'  => 'text',
		],
		'description'          => [
			'label' => 'Meta Description',
			'type'  => 'textarea',
			'rows'  => 3,
		],
		'keywords'             => [
			'label' => 'Meta Keywords',
			'type'  => 'textarea',
			'rows'  => 2,
		],
		'robots'               => [
			'label' => 'Robots',
			'type'  => 'text',
		],
		'canonical'            => [
			'label' => 'Canonical URL',
			'type'  => 'url',
		],
		'og_title'             => [
			'label' => 'OG Title',
			'type'  => 'text',
		],
		'og_description'       => [
			'label' => 'OG Description',
			'type'  => 'textarea',
			'rows'  => 3,
		],
		'og_image'             => [
			'label' => 'OG Image URL',
			'type'  => 'url',
		],
		'og_type'              => [
			'label' => 'OG Type',
			'type'  => 'text',
		],
		'twitter_card'         => [
			'label' => 'Twitter Card',
			'type'  => 'text',
		],
		'twitter_title'        => [
			'label' => 'Twitter Title',
			'type'  => 'text',
		],
		'twitter_description'  => [
			'label' => 'Twitter Description',
			'type'  => 'textarea',
			'rows'  => 3,
		],
		'twitter_image'        => [
			'label' => 'Twitter Image URL',
			'type'  => 'url',
		],
	];
}

/**
 * @return list<string>
 */
function seo_meta_post_types(): array {
	return [ 'post', 'page' ];
}

/**
 * @param string $field
 */
function seo_meta_key( string $field ): string {
	return 'seo_' . $field;
}

add_action(
	'init',
	function () {
		foreach ( seo_meta_post_types() as $post_type ) {
			foreach ( array_keys( seo_meta_field_definitions() ) as $field ) {
				register_post_meta(
					$post_type,
					seo_meta_key( $field ),
					[
						'show_in_rest'  => true,
						'single'        => true,
						'type'          => 'string',
						'auth_callback' => static function (): bool {
							return current_user_can( 'edit_posts' );
						},
					]
				);
			}
		}
	}
);

add_action(
	'add_meta_boxes',
	function () {
		foreach ( seo_meta_post_types() as $post_type ) {
			add_meta_box(
				'seo_meta_box',
				'SEO / Open Graph / Twitter',
				'seo_meta_render_meta_box',
				$post_type,
				'normal',
				'high'
			);
		}
	}
);

/**
 * @param WP_Post $post
 */
function seo_meta_render_meta_box( $post ): void {
	wp_nonce_field( 'seo_meta_save', 'seo_meta_nonce' );

	echo '<div class="seo-meta-fields" style="display:grid;gap:12px;">';

	foreach ( seo_meta_field_definitions() as $field => $config ) {
		$meta_key = seo_meta_key( $field );
		$value    = (string) get_post_meta( $post->ID, $meta_key, true );
		$label    = (string) ( $config['label'] ?? $field );
		$type     = (string) ( $config['type'] ?? 'text' );
		$rows     = (int) ( $config['rows'] ?? 2 );

		echo '<div>';
		echo '<label for="' . esc_attr( $meta_key ) . '" style="display:block;font-weight:600;margin-bottom:4px;">';
		echo esc_html( $label );
		echo '</label>';

		if ( $type === 'textarea' ) {
			printf(
				'<textarea id="%1$s" name="%1$s" rows="%2$d" style="width:100%%;">%3$s</textarea>',
				esc_attr( $meta_key ),
				$rows,
				esc_textarea( $value )
			);
		} else {
			printf(
				'<input type="text" id="%1$s" name="%1$s" value="%2$s" style="width:100%%;" />',
				esc_attr( $meta_key ),
				esc_attr( $value )
			);
		}

		echo '</div>';
	}

	echo '</div>';
}

add_action(
	'save_post',
	function ( int $post_id ): void {
		if ( ! isset( $_POST['seo_meta_nonce'] ) ) {
			return;
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['seo_meta_nonce'] ) ), 'seo_meta_save' ) ) {
			return;
		}

		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		foreach ( array_keys( seo_meta_field_definitions() ) as $field ) {
			$meta_key = seo_meta_key( $field );

			if ( ! isset( $_POST[ $meta_key ] ) ) {
				continue;
			}

			$raw = wp_unslash( $_POST[ $meta_key ] );

			if ( ! is_string( $raw ) ) {
				continue;
			}

			$value = sanitize_text_field( $raw );

			if ( in_array( $field, [ 'description', 'keywords', 'og_description', 'twitter_description' ], true ) ) {
				$value = sanitize_textarea_field( $raw );
			}

			if ( in_array( $field, [ 'canonical', 'og_image', 'twitter_image' ], true ) ) {
				$value = esc_url_raw( $raw );
			}

			if ( $value === '' ) {
				delete_post_meta( $post_id, $meta_key );
				continue;
			}

			update_post_meta( $post_id, $meta_key, $value );
		}
	}
);

/**
 * @return array<string, string>
 */
function seo_meta_collect_for_post( int $post_id ): array {
	$values = [];

	foreach ( array_keys( seo_meta_field_definitions() ) as $field ) {
		$value = trim( (string) get_post_meta( $post_id, seo_meta_key( $field ), true ) );
		if ( $value !== '' ) {
			$values[ $field ] = $value;
		}
	}

	return $values;
}

/**
 * @param array<string, string> $values
 */
function seo_meta_resolve_context( int $post_id, array $values ): array {
	$post = get_post( $post_id );

	$title = $values['title'] ?? ( $post instanceof WP_Post ? get_the_title( $post ) : '' );
	$description = $values['description'] ?? '';
	$keywords = $values['keywords'] ?? '';
	$robots = $values['robots'] ?? '';
	$canonical = $values['canonical'] ?? '';

	if ( $canonical === '' && $post instanceof WP_Post ) {
		$permalink = get_permalink( $post );
		if ( is_string( $permalink ) && $permalink !== '' ) {
			$canonical = $permalink;
		}
	}

	$og_title = $values['og_title'] ?? $title;
	$og_description = $values['og_description'] ?? $description;
	$og_image = $values['og_image'] ?? '';

	if ( $og_image === '' && $post instanceof WP_Post && has_post_thumbnail( $post ) ) {
		$thumb = get_the_post_thumbnail_url( $post, 'large' );
		if ( is_string( $thumb ) && $thumb !== '' ) {
			$og_image = $thumb;
		}
	}

	$og_type = $values['og_type'] ?? '';
	if ( $og_type === '' && $post instanceof WP_Post ) {
		$og_type = $post->post_type === 'post' ? 'article' : 'website';
	}

	$twitter_card = $values['twitter_card'] ?? 'summary_large_image';
	$twitter_title = $values['twitter_title'] ?? $og_title;
	$twitter_description = $values['twitter_description'] ?? $og_description;
	$twitter_image = $values['twitter_image'] ?? $og_image;

	return [
		'title'               => $title,
		'description'         => $description,
		'keywords'            => $keywords,
		'robots'              => $robots,
		'canonical'           => $canonical,
		'og_title'            => $og_title,
		'og_description'      => $og_description,
		'og_image'            => $og_image,
		'og_type'             => $og_type,
		'twitter_card'        => $twitter_card,
		'twitter_title'       => $twitter_title,
		'twitter_description' => $twitter_description,
		'twitter_image'       => $twitter_image,
	];
}

add_filter(
	'document_title_parts',
	function ( array $parts ): array {
		if ( ! is_singular( seo_meta_post_types() ) ) {
			return $parts;
		}

		$post_id = get_queried_object_id();
		if ( $post_id <= 0 ) {
			return $parts;
		}

		$seo_title = trim( (string) get_post_meta( $post_id, seo_meta_key( 'title' ), true ) );
		if ( $seo_title === '' ) {
			return $parts;
		}

		$parts['title'] = $seo_title;

		return $parts;
	}
);

add_action(
	'wp_head',
	function (): void {
		if ( ! is_singular( seo_meta_post_types() ) ) {
			return;
		}

		$post_id = get_queried_object_id();
		if ( $post_id <= 0 ) {
			return;
		}

		$resolved = seo_meta_resolve_context( $post_id, seo_meta_collect_for_post( $post_id ) );
		$locale   = str_replace( '-', '_', get_locale() );
		$site_name = get_bloginfo( 'name' );

		if ( $resolved['description'] !== '' ) {
			printf(
				"<meta name=\"description\" content=\"%s\" />\n",
				esc_attr( $resolved['description'] )
			);
		}

		if ( $resolved['keywords'] !== '' ) {
			printf(
				"<meta name=\"keywords\" content=\"%s\" />\n",
				esc_attr( $resolved['keywords'] )
			);
		}

		if ( $resolved['robots'] !== '' ) {
			printf(
				"<meta name=\"robots\" content=\"%s\" />\n",
				esc_attr( $resolved['robots'] )
			);
		}

		if ( $resolved['canonical'] !== '' ) {
			printf(
				"<link rel=\"canonical\" href=\"%s\" />\n",
				esc_url( $resolved['canonical'] )
			);
		}

		if ( $resolved['og_title'] !== '' ) {
			printf(
				"<meta property=\"og:title\" content=\"%s\" />\n",
				esc_attr( $resolved['og_title'] )
			);
		}

		if ( $resolved['og_description'] !== '' ) {
			printf(
				"<meta property=\"og:description\" content=\"%s\" />\n",
				esc_attr( $resolved['og_description'] )
			);
		}

		if ( $resolved['og_image'] !== '' ) {
			printf(
				"<meta property=\"og:image\" content=\"%s\" />\n",
				esc_url( $resolved['og_image'] )
			);
			echo "<meta property=\"og:image:width\" content=\"1200\" />\n";
			echo "<meta property=\"og:image:height\" content=\"630\" />\n";
		}

		if ( $resolved['canonical'] !== '' ) {
			printf(
				"<meta property=\"og:url\" content=\"%s\" />\n",
				esc_url( $resolved['canonical'] )
			);
		}

		printf(
			"<meta property=\"og:type\" content=\"%s\" />\n",
			esc_attr( $resolved['og_type'] )
		);
		printf(
			"<meta property=\"og:locale\" content=\"%s\" />\n",
			esc_attr( $locale )
		);

		if ( is_string( $site_name ) && $site_name !== '' ) {
			printf(
				"<meta property=\"og:site_name\" content=\"%s\" />\n",
				esc_attr( $site_name )
			);
		}

		printf(
			"<meta name=\"twitter:card\" content=\"%s\" />\n",
			esc_attr( $resolved['twitter_card'] )
		);

		if ( $resolved['twitter_title'] !== '' ) {
			printf(
				"<meta name=\"twitter:title\" content=\"%s\" />\n",
				esc_attr( $resolved['twitter_title'] )
			);
		}

		if ( $resolved['twitter_description'] !== '' ) {
			printf(
				"<meta name=\"twitter:description\" content=\"%s\" />\n",
				esc_attr( $resolved['twitter_description'] )
			);
		}

		if ( $resolved['twitter_image'] !== '' ) {
			printf(
				"<meta name=\"twitter:image\" content=\"%s\" />\n",
				esc_url( $resolved['twitter_image'] )
			);
		}
	},
	1
);
