<?php
/**
 * Author Override functionality for BW AI Schema Pro
 * Handles overriding WordPress author display with custom authors
 *
 * @package BW_AI_Schema_Pro
 */

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

class BW_Schema_Author_Override {
	
	/**
	 * Initialize the class
	 */
	/**
	 * Class-level recursion protection flag
	 */
	private static $is_processing = false;

	public static function init() {
		// Admin-only: warn editors when a team page is used as a schema author
		// but has no Linked WordPress User. Prevents the recurring drift that
		// breaks the /author/X/ → team-page 301 redirect. Runs in admin only.
		if ( is_admin() && ! wp_doing_ajax() ) {
			add_action( 'admin_notices', array( __CLASS__, 'maybe_show_unlinked_team_notice' ) );
			return;
		}

		// Skip on AJAX (no point overriding bylines for XHR'd content).
		if ( wp_doing_ajax() ) {
			return;
		}

		// Display-name override for the legacy `the_author()` template tag.
		add_filter( 'the_author', array( __CLASS__, 'filter_author_name' ), 9999, 1 );

		// Server-side byline overrides (since 2.2.0).
		//
		// Kadence and similar modern themes build the byline as an anchor whose
		// text comes from get_the_author_meta( "display_name" ) and whose href
		// is preferentially get_the_author_meta( "url" ) (the user's profile
		// website URL), falling back to get_author_posts_url( author_id ) only
		// when user_url is empty. None of those are reachable from the
		// the_author filter alone, so we hook three things:
		//   - get_the_author_display_name   -> visible name
		//   - get_the_author_user_url       -> user-profile URL (Kadence's
		//                                      preferred href source)
		//   - author_link                   -> fallback href source for
		//                                      themes that skip user_url
		// All three are scoped to the singular-post context with the same
		// author the byline is being rendered for so they don't bleed into
		// comments, widgets, author archives, etc.
		add_filter( 'get_the_author_display_name', array( __CLASS__, 'filter_author_display_name' ), 10, 2 );
		// NOTE: WP rewrites the `url` field to `user_url` internally inside
		// `get_the_author_meta()`, so the filter name is `get_the_author_user_url`
		// (not `get_the_author_url`).
		add_filter( 'get_the_author_user_url', array( __CLASS__, 'filter_author_user_url' ), 10, 2 );
		add_filter( 'author_link', array( __CLASS__, 'filter_author_link_url' ), 10, 2 );

		// Check if author box should be disabled
		add_action( 'wp', array( __CLASS__, 'maybe_disable_author_box' ) );

		// JavaScript safety net for stubborn themes that bypass the filters
		// above (some themes hardcode display name via $authordata->display_name
		// without going through get_the_author_meta). Kept as a belt-and-braces
		// layer; the server-side filters are the primary fix.
		add_action( 'wp_footer', array( __CLASS__, 'output_author_js' ), 999 );
	}

	/**
	 * Resolve the configured team-member author for a post, if any.
	 *
	 * Single source of truth used by both `filter_author_link_url` and
	 * `filter_author_display_name`. Returns null if the post has no team-member
	 * override, or if the referenced team post is missing / unpublished.
	 *
	 * @param int $post_id
	 * @return array{id:int,name:string,url:string}|null
	 */
	private static function resolve_team_member_for_post( $post_id ) {
		$post_id = (int) $post_id;
		if ( ! $post_id ) {
			return null;
		}

		$authors = get_post_meta( $post_id, '_bw_schema_multiple_authors', true );
		if ( empty( $authors ) || ! is_array( $authors ) ) {
			return null;
		}

		// First-author wins for byline display purposes. (Multi-author bylines
		// are a future improvement — kept simple here so the common single-author
		// case is bulletproof.)
		$first = reset( $authors );
		if ( ! is_array( $first ) || empty( $first['type'] ) || 'team_member' !== $first['type'] ) {
			return null;
		}

		$team_id = isset( $first['team_member_id'] ) ? (int) $first['team_member_id'] : 0;
		if ( ! $team_id ) {
			return null;
		}

		$team = get_post( $team_id );
		if ( ! $team || 'publish' !== $team->post_status ) {
			return null;
		}

		$url = get_permalink( $team );
		if ( ! $url ) {
			return null;
		}

		return array(
			'id'   => $team_id,
			'name' => $team->post_title,
			'url'  => $url,
		);
	}

	/**
	 * `author_link` filter — rewrite the byline href to the team-member page
	 * when the singular post has a team-member author override.
	 *
	 * Narrowly scoped: only rewrites when the link being filtered is for the
	 * current singular post's author. Comments, widgets, and author-archive
	 * links elsewhere on the page are untouched.
	 *
	 * @param string $link
	 * @param int    $author_id
	 * @return string
	 */
	public static function filter_author_link_url( $link, $author_id = 0 ) {
		if ( is_admin() ) {
			return $link;
		}

		$post = get_post();
		if ( ! $post ) {
			return $link;
		}

		// Only override when the filter is firing for THIS post's WP author.
		// Otherwise pass through (e.g., a comment-author widget on the same page).
		if ( $author_id && (int) $author_id !== (int) $post->post_author ) {
			return $link;
		}

		$tm = self::resolve_team_member_for_post( $post->ID );
		if ( ! $tm ) {
			return $link;
		}

		return $tm['url'];
	}

	/**
	 * `get_the_author_url` filter — rewrite the user's profile-URL field when
	 * it's being read for the byline of a singular post with a team-member
	 * author override. Kadence checks `get_the_author_meta('url')` first when
	 * picking the byline href, so if it returns the team page URL, the byline
	 * lands correctly without falling through to `author_link` at all.
	 *
	 * Scoped just like `filter_author_display_name`: singular context, same
	 * author the queried post belongs to. Other reads of user_url (comments,
	 * user-profile widgets, etc.) are untouched.
	 *
	 * @param string $url
	 * @param int    $user_id
	 * @return string
	 */
	public static function filter_author_user_url( $url, $user_id ) {
		if ( is_admin() ) {
			return $url;
		}
		if ( ! is_singular() ) {
			return $url;
		}

		$post = get_post();
		if ( ! $post ) {
			return $url;
		}

		if ( (int) $user_id !== (int) $post->post_author ) {
			return $url;
		}

		$tm = self::resolve_team_member_for_post( $post->ID );
		if ( ! $tm ) {
			return $url;
		}

		return $tm['url'];
	}

	/**
	 * `get_the_author_display_name` filter — rewrite the byline display name
	 * to the team-member's name. Pairs with `filter_author_link_url` so the
	 * server-rendered byline is correct without needing the JS safety net for
	 * Kadence-style themes that use `get_the_author_meta('display_name')`.
	 *
	 * @param string $name
	 * @param int    $user_id
	 * @return string
	 */
	public static function filter_author_display_name( $name, $user_id ) {
		if ( is_admin() ) {
			return $name;
		}
		if ( ! is_singular() ) {
			return $name;
		}

		$post = get_post();
		if ( ! $post ) {
			return $name;
		}

		if ( (int) $user_id !== (int) $post->post_author ) {
			return $name;
		}

		$tm = self::resolve_team_member_for_post( $post->ID );
		if ( ! $tm ) {
			return $name;
		}

		return $tm['name'];
	}

	/**
	 * Admin-only: warn editors on the team CPT edit screen when a team page is
	 * referenced as a schema author but has no Linked WordPress User. This is
	 * the configuration drift that historically broke the /author/X/ → team
	 * redirect. We can't make the field mandatory (some team members aren't
	 * authors on the site at all), but we can make the bad state loud.
	 */
	public static function maybe_show_unlinked_team_notice() {
		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( ! $screen || 'post' !== $screen->base ) {
			return;
		}

		if ( ! class_exists( 'BW_Schema_Team_Member' ) ) {
			return;
		}

		$team_post_type = BW_Schema_Team_Member::get_team_post_type();
		if ( ! $team_post_type || $screen->post_type !== $team_post_type ) {
			return;
		}

		$post = get_post();
		if ( ! $post || $post->post_type !== $team_post_type ) {
			return;
		}

		// Already linked → nothing to warn about.
		$linked = get_post_meta( $post->ID, '_bw_schema_linked_user', true );
		if ( $linked ) {
			return;
		}

		// Count posts that reference THIS team page as a schema author.
		$usage_count = self::count_posts_using_team_member( $post->ID );
		if ( $usage_count < 1 ) {
			return;
		}

		// Render the notice.
		$message = sprintf(
			/* translators: %d: number of posts using this team page as an author */
			_n(
				'This team page is used as the schema author on %d post, but no Linked WordPress User is set. The byline link on those posts works (it points here), but direct visits to /author/USERNAME/ will not redirect here.',
				'This team page is used as the schema author on %d posts, but no Linked WordPress User is set. The byline links on those posts work (they point here), but direct visits to /author/USERNAME/ will not redirect here.',
				$usage_count,
				'bw-ai-schema-pro'
			),
			$usage_count
		);
		$fix = esc_html__( 'Scroll down to "Linked WordPress User" in the Person Schema Settings box and pick the WP user this team member represents.', 'bw-ai-schema-pro' );

		printf(
			'<div class="notice notice-warning"><p><strong>%s</strong> %s</p><p>%s</p></div>',
			esc_html__( 'BW AI Schema Pro:', 'bw-ai-schema-pro' ),
			esc_html( $message ),
			$fix
		);
	}

	/**
	 * Count posts that have this team page selected as their schema author.
	 *
	 * Uses a coarse SQL LIKE to narrow candidates (since the meta value is a
	 * serialized array), then verifies each match in PHP. Run on an admin
	 * screen for a single team page — performance is fine.
	 *
	 * @param int $team_post_id
	 * @return int
	 */
	private static function count_posts_using_team_member( $team_post_id ) {
		global $wpdb;

		$team_post_id = (int) $team_post_id;
		if ( ! $team_post_id ) {
			return 0;
		}

		// Coarse SQL filter: posts whose _bw_schema_multiple_authors meta_value
		// contains "team_member_id" and the team post ID anywhere. Catches both
		// string ("s:N:") and int ("i:") serializations.
		$candidates = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT post_id FROM {$wpdb->postmeta}
				 WHERE meta_key = '_bw_schema_multiple_authors'
				   AND meta_value LIKE %s
				   AND meta_value LIKE %s",
				'%' . $wpdb->esc_like( 'team_member_id' ) . '%',
				'%' . $wpdb->esc_like( (string) $team_post_id ) . '%'
			)
		);

		if ( empty( $candidates ) ) {
			return 0;
		}

		$count = 0;
		foreach ( $candidates as $pid ) {
			$authors = get_post_meta( (int) $pid, '_bw_schema_multiple_authors', true );
			if ( ! is_array( $authors ) ) {
				continue;
			}
			foreach ( $authors as $a ) {
				if ( ! is_array( $a ) ) {
					continue;
				}
				$type = isset( $a['type'] ) ? (string) $a['type'] : '';
				$tid  = isset( $a['team_member_id'] ) ? (int) $a['team_member_id'] : 0;
				if ( 'team_member' === $type && $tid === $team_post_id ) {
					$count++;
					break; // count each post once even if listed multiple times
				}
			}
		}

		return $count;
	}
	
	/**
	 * Maybe disable the default author box
	 */
	public static function maybe_disable_author_box() {
		if ( ! is_singular() ) {
			return;
		}
		
		global $post;
		if ( ! $post ) {
			return;
		}
		
		$disable_author_box = get_post_meta( $post->ID, '_bw_schema_disable_default_author', true );
		if ( $disable_author_box ) {
			// Remove Kadence author box
			remove_action( 'kadence_single_after_content', 'Kadence\single_after_content', 20 );
			remove_action( 'kadence_single_after_content', 'kadence_author_box', 20 );
			
			// Remove Genesis author box
			remove_action( 'genesis_after_entry', 'genesis_do_author_box_single', 8 );
			
			// Remove common theme author boxes
			remove_filter( 'the_content', 'add_author_box_to_content' );
			
			// Remove various theme author box actions
			remove_action( 'generate_after_content', 'generate_do_author_box' );
			remove_action( 'astra_entry_after', 'astra_single_post_author_box' );
			remove_action( 'oceanwp_after_single_post_content', 'oceanwp_author_bio', 10 );
			
			// Add custom filter for themes to check
			add_filter( 'bw_schema_disable_author_box', '__return_true' );
			add_filter( 'show_author_box', '__return_false' );
			add_filter( 'display_author_box', '__return_false' );
		}
	}
	
	/**
	 * Get the custom author for the current post
	 */
	private static function get_custom_author() {
		global $post;
		
		if ( ! $post ) {
			return null;
		}
		
		// Check for multiple authors first
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			// Return the first author for display purposes
			$first_author = $multiple_authors[0];
			
			switch ( $first_author['type'] ) {
				// v2.0: Team Member Author
				case 'team_member':
					if ( ! empty( $first_author['team_member_id'] ) ) {
						$team_member_id = intval( $first_author['team_member_id'] );
						$team_member = get_post( $team_member_id );
						if ( $team_member && $team_member->post_status === 'publish' ) {
							// Get team member metadata
							$job_title = get_post_meta( $team_member_id, 'job_title', true )
								?: get_post_meta( $team_member_id, '_job_title', true );
							$bio = get_post_meta( $team_member_id, 'bio', true )
								?: get_post_meta( $team_member_id, '_bio', true )
								?: $team_member->post_content;
							$image = get_the_post_thumbnail_url( $team_member_id, 'medium' );
							$email = get_post_meta( $team_member_id, 'email', true )
								?: get_post_meta( $team_member_id, '_email', true );
							$credentials = get_post_meta( $team_member_id, '_bw_schema_credentials', true );

							return array(
								'id' => 'team_' . $team_member_id,
								'name' => $team_member->post_title,
								'jobTitle' => $job_title,
								'description' => $bio,
								'image' => $image,
								'email' => $email,
								'credentials' => $credentials,
								'url' => get_permalink( $team_member_id ),
								'social' => array(
									'website' => get_post_meta( $team_member_id, 'website', true )
										?: get_post_meta( $team_member_id, '_website', true ),
									'linkedin' => get_post_meta( $team_member_id, 'linkedin', true )
										?: get_post_meta( $team_member_id, '_linkedin', true ),
									'twitter' => get_post_meta( $team_member_id, 'twitter', true )
										?: get_post_meta( $team_member_id, '_twitter', true ),
								),
								'_team_member_id' => $team_member_id,
							);
						}
					}
					return null;

				// v2.0: External Author (saved in settings)
				case 'external_saved':
					if ( ! empty( $first_author['external_author_id'] ) ) {
						$external_authors = get_option( 'bw_schema_external_authors', array() );
						$author_id = $first_author['external_author_id'];
						if ( isset( $external_authors[ $author_id ] ) ) {
							$ext = $external_authors[ $author_id ];
							return array(
								'id' => $author_id,
								'name' => $ext['name'] ?? '',
								'jobTitle' => $ext['jobTitle'] ?? '',
								'description' => $ext['description'] ?? '',
								'image' => $ext['image'] ?? '',
								'email' => '',
								'credentials' => $ext['credentials'] ?? '',
								'url' => $ext['website'] ?? '',
								'social' => array(
									'website' => $ext['website'] ?? '',
									'linkedin' => $ext['social']['linkedin'] ?? '',
									'twitter' => $ext['social']['twitter'] ?? '',
								),
							);
						}
					}
					return null;

				// Legacy: WordPress User
				case 'wordpress':
					// Return null to use WordPress user
					return null;

				// Legacy: Custom Author (from old settings)
				case 'custom':
					$custom_authors = get_option( 'bw_schema_custom_authors', array() );
					foreach ( $custom_authors as $author ) {
						if ( $author['id'] == $first_author['custom_author_id'] ) {
							return $author;
						}
					}
					break;

				// External (One-time) - inline data
				case 'external':
					// Convert external author data to custom author format
					if ( ! empty( $first_author['external']['name'] ) ) {
						return array(
							'id' => 'external_' . sanitize_title( $first_author['external']['name'] ),
							'name' => $first_author['external']['name'],
							'jobTitle' => $first_author['external']['job_title'] ?? '',
							'description' => $first_author['external']['bio'] ?? '',
							'image' => $first_author['external']['image'] ?? '',
							'email' => $first_author['external']['email'] ?? '',
							'social' => array(
								'website' => $first_author['external']['website'] ?? '',
								'linkedin' => $first_author['external']['linkedin'] ?? '',
								'twitter' => $first_author['external']['twitter'] ?? '',
							)
						);
					}
					break;
			}
		}
		
		// Legacy single custom author support
		$custom_author_id = get_post_meta( $post->ID, '_bw_schema_custom_author', true );
		
		// If no custom author selected, use default
		if ( empty( $custom_author_id ) ) {
			$custom_authors = get_option( 'bw_schema_custom_authors', array() );
			foreach ( $custom_authors as $author ) {
				if ( ! empty( $author['isDefault'] ) ) {
					return $author;
				}
			}
			return null;
		}
		
		// Get the specific custom author
		$custom_authors = get_option( 'bw_schema_custom_authors', array() );
		foreach ( $custom_authors as $author ) {
			if ( $author['id'] == $custom_author_id ) {
				return $author;
			}
		}
		
		return null;
	}
	
	/**
	 * Filter the author name
	 */
	public static function filter_author_name( $author_name ) {
		// Prevent infinite recursion
		if ( self::$is_processing ) {
			return $author_name;
		}
		self::$is_processing = true;

		global $post;

		if ( ! $post ) {
			self::$is_processing = false;
			return $author_name;
		}

		// Get all authors for this post
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );

		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );

			foreach ( $multiple_authors as $index => $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}
				
				if ( $name ) {
					$author_names[] = $name;
				}
			}

			if ( ! empty( $author_names ) ) {
				$formatted = self::format_author_names( $author_names );
				self::$is_processing = false;
				return $formatted;
			}
		}

		// Fall back to single custom author
		$custom_author = self::get_custom_author();
		if ( $custom_author ) {
			self::$is_processing = false;
			return $custom_author['name'];
		}

		self::$is_processing = false;
		return $author_name;
	}
	
	/**
	 * Format multiple author names for display
	 */
	private static function format_author_names( $author_names, $with_links = false, $author_data_array = null ) {
		$count = count( $author_names );

		if ( ! $with_links || ! $author_data_array ) {
			// Simple text format
			if ( $count === 1 ) {
				return $author_names[0];
			} elseif ( $count === 2 ) {
				return $author_names[0] . ' and ' . $author_names[1];
			} elseif ( $count === 3 ) {
				return $author_names[0] . ', ' . $author_names[1] . ' and ' . $author_names[2];
			} elseif ( $count === 4 ) {
				return $author_names[0] . ', ' . $author_names[1] . ', ' . $author_names[2] . ' and ' . $author_names[3];
			} elseif ( $count === 5 ) {
				return $author_names[0] . ', ' . $author_names[1] . ', ' . $author_names[2] . ', ' . $author_names[3] . ' and ' . $author_names[4];
			} else {
				// Show first three authors and count of others for 6+ authors
				$others_count = $count - 3;
				return $author_names[0] . ', ' . $author_names[1] . ', ' . $author_names[2] . ' and ' . $others_count . ' ' . ( $others_count === 1 ? 'other' : 'others' );
			}
		}
		
		// Format with links
		$formatted_authors = array();
		$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );

		foreach ( $author_data_array as $index => $author_data ) {
			$name = $author_names[$index];
			$url = '';
			$is_external = false; // Track if URL is external (should open in new tab)

			switch ( $author_data['type'] ) {
				// v2.0: Team Member Author - internal link
				case 'team_member':
					if ( ! empty( $author_data['team_member_id'] ) ) {
						$url = get_permalink( intval( $author_data['team_member_id'] ) );
						$is_external = false;
					}
					break;

				// v2.0: External Author (saved in settings) - external link
				case 'external_saved':
					if ( ! empty( $author_data['external_author_id'] ) ) {
						$external_authors = get_option( 'bw_schema_external_authors', array() );
						$author_id = $author_data['external_author_id'];
						if ( isset( $external_authors[ $author_id ] ) ) {
							$url = $external_authors[ $author_id ]['website'] ?? '';
							$is_external = true;
						}
					}
					break;

				// Legacy: WordPress User - internal link
				case 'wordpress':
					if ( ! empty( $author_data['wordpress_user_id'] ) ) {
						$user = get_userdata( $author_data['wordpress_user_id'] );
						if ( $user ) {
							// Check if user has linked team member
							$linked_team = self::get_team_member_for_user( $user->ID );
							if ( $linked_team ) {
								$url = get_permalink( $linked_team );
							} else {
								$url = get_author_posts_url( $user->ID );
							}
							$is_external = false;
						}
					}
					break;

				// Legacy: Custom Author - depends on URL source
				case 'custom':
					if ( ! empty( $author_data['custom_author_id'] ) ) {
						foreach ( $custom_authors_list as $custom_author ) {
							if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
								// Determine author URL - track if internal or external
								if ( ! empty( $custom_author['teamPageUrl'] ) ) {
									$url = $custom_author['teamPageUrl'];
									$is_external = true; // Custom URL could be external
								} elseif ( ! empty( $custom_author['teamPageId'] ) && $custom_author['teamPageId'] !== 'custom' ) {
									$url = get_permalink( $custom_author['teamPageId'] );
									$is_external = false; // Internal team page
								} elseif ( ! empty( $custom_author['social']['website'] ) ) {
									$url = $custom_author['social']['website'];
									$is_external = true; // External website
								}
								break;
							}
						}
					}
					break;

				// External (One-time) - external link
				case 'external':
					if ( ! empty( $author_data['external']['website'] ) ) {
						$url = $author_data['external']['website'];
						$is_external = true;
					}
					break;
			}

			if ( $url ) {
				// Only add target="_blank" for external URLs
				$target_attr = $is_external ? ' target="_blank" rel="noopener noreferrer"' : '';
				$formatted_authors[] = sprintf( '<a href="%s" rel="author"%s>%s</a>', esc_url( $url ), $target_attr, esc_html( $name ) );
			} else {
				$formatted_authors[] = esc_html( $name );
			}
		}
		
		// Join formatted authors
		if ( count( $formatted_authors ) === 1 ) {
			return $formatted_authors[0];
		} elseif ( count( $formatted_authors ) === 2 ) {
			return $formatted_authors[0] . ' and ' . $formatted_authors[1];
		} elseif ( count( $formatted_authors ) === 3 ) {
			return $formatted_authors[0] . ', ' . $formatted_authors[1] . ' and ' . $formatted_authors[2];
		} elseif ( count( $formatted_authors ) === 4 ) {
			return $formatted_authors[0] . ', ' . $formatted_authors[1] . ', ' . $formatted_authors[2] . ' and ' . $formatted_authors[3];
		} elseif ( count( $formatted_authors ) === 5 ) {
			return $formatted_authors[0] . ', ' . $formatted_authors[1] . ', ' . $formatted_authors[2] . ', ' . $formatted_authors[3] . ' and ' . $formatted_authors[4];
		} else {
			// Show first three authors and count of others for 6+ authors
			$others_count = count( $formatted_authors ) - 3;
			return $formatted_authors[0] . ', ' . $formatted_authors[1] . ', ' . $formatted_authors[2] . ' and ' . $others_count . ' ' . ( $others_count === 1 ? 'other' : 'others' );
		}
	}
	
	/**
	 * Filter the author link
	 */
	public static function filter_author_link( $link ) {
		global $post;
		
		if ( ! $post ) {
			return $link;
		}
		
		// Get all authors for this post
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );
		
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );
			
			foreach ( $multiple_authors as $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}

				if ( $name ) {
					$author_names[] = $name;
				}
			}

			if ( ! empty( $author_names ) ) {
				$formatted_names = self::format_author_names( $author_names );
				// Return formatted names without link for multiple authors
				return esc_html( $formatted_names );
			}
		}

		// Fall back to single custom author
		$custom_author = self::get_custom_author();
		
		if ( $custom_author ) {
			// If custom author has a website, link to that
			if ( ! empty( $custom_author['social']['website'] ) ) {
				return sprintf(
					'<a href="%s" title="%s" rel="author external" target="_blank">%s</a>',
					esc_url( $custom_author['social']['website'] ),
					esc_attr( sprintf( __( 'Visit %s website', 'bw-ai-schema-pro' ), $custom_author['name'] ) ),
					esc_html( $custom_author['name'] )
				);
			}
			// Otherwise just return the name without a link
			return esc_html( $custom_author['name'] );
		}
		
		return $link;
	}
	
	/**
	 * Filter get_the_author_link
	 *
	 * Returns the author name as a link. For v2.0 authors (team_member, external_saved),
	 * links to the team page or website. Works for both single and multiple authors.
	 */
	public static function filter_get_author_link() {
		global $post, $authordata;

		if ( ! is_object( $authordata ) ) {
			return '';
		}

		// Get all authors for this post
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );

		// Handle v2.0 authors (single or multiple)
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );

			foreach ( $multiple_authors as $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}

				if ( $name ) {
					$author_names[] = $name;
				}
			}

			if ( ! empty( $author_names ) ) {
				// Return formatted names with individual links
				return self::format_author_names( $author_names, true, $multiple_authors );
			}
		}

		// Default behavior - check if WP user has a linked team member
		$linked_team_member = self::get_team_member_for_user( $authordata->ID );
		if ( $linked_team_member ) {
			$url = get_permalink( $linked_team_member );
		} else {
			$url = get_author_posts_url( $authordata->ID, $authordata->user_nicename );
		}

		$link = sprintf(
			'<a href="%1$s" title="%2$s" rel="author">%3$s</a>',
			esc_url( $url ),
			esc_attr( sprintf( __( 'Posts by %s' ), get_the_author() ) ),
			get_the_author()
		);

		return $link;
	}

	/**
	 * Get team member ID linked to a WordPress user
	 *
	 * @param int $user_id WordPress user ID
	 * @return int|null Team member post ID or null if not found
	 */
	private static function get_team_member_for_user( $user_id ) {
		if ( ! class_exists( 'BW_Schema_Team_Member' ) ) {
			return null;
		}

		$team_post_type = BW_Schema_Team_Member::get_team_post_type();
		if ( ! $team_post_type ) {
			return null;
		}

		$team_members = get_posts( array(
			'post_type'      => $team_post_type,
			'posts_per_page' => 1,
			'meta_query'     => array(
				array(
					'key'   => '_bw_schema_linked_user',
					'value' => $user_id,
				),
			),
		) );

		return ! empty( $team_members ) ? $team_members[0]->ID : null;
	}
	
	/**
	 * Filter the author posts link
	 */
	public static function filter_author_posts_link( $link ) {
		global $post;
		
		if ( ! $post ) {
			return $link;
		}
		
		// Get all authors for this post
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );
		
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );
			
			foreach ( $multiple_authors as $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}

				if ( $name ) {
					$author_names[] = $name;
				}
			}

			if ( ! empty( $author_names ) ) {
				$formatted_names = self::format_author_names( $author_names );
				// Replace the author name in the link HTML
				$link = preg_replace( '/>([^<]+)<\/a>/', '>' . esc_html( $formatted_names ) . '</a>', $link );
				return $link;
			}
		}

		// Fall back to single custom author
		$custom_author = self::get_custom_author();
		if ( $custom_author ) {
			// Replace the author name in the link
			$link = preg_replace( '/>([^<]+)<\/a>/', '>' . esc_html( $custom_author['name'] ) . '</a>', $link );
		}

		return $link;
	}
	
	/**
	 * Filter the author description
	 */
	public static function filter_author_description( $description, $user_id = null ) {
		$custom_author = self::get_custom_author();
		
		if ( $custom_author && ! empty( $custom_author['description'] ) ) {
			return $custom_author['description'];
		}
		
		return $description;
	}
	
	/**
	 * Filter the author avatar
	 */
	public static function filter_author_avatar( $avatar, $id_or_email, $size, $default, $alt, $args ) {
		// Only filter on single posts
		if ( ! is_singular() ) {
			return $avatar;
		}
		
		$custom_author = self::get_custom_author();
		
		if ( $custom_author && ! empty( $custom_author['image'] ) ) {
			$alt = ! empty( $alt ) ? $alt : $custom_author['name'];
			$avatar = sprintf(
				'<img alt="%s" src="%s" class="avatar avatar-%d photo" height="%d" width="%d" loading="lazy" decoding="async"/>',
				esc_attr( $alt ),
				esc_url( $custom_author['image'] ),
				(int) $size,
				(int) $size,
				(int) $size
			);
		}
		
		return $avatar;
	}
	
	/**
	 * Filter the author avatar URL
	 */
	public static function filter_author_avatar_url( $url, $id_or_email, $args ) {
		// Only filter on single posts
		if ( ! is_singular() ) {
			return $url;
		}
		
		$custom_author = self::get_custom_author();
		
		if ( $custom_author && ! empty( $custom_author['image'] ) ) {
			return $custom_author['image'];
		}
		
		return $url;
	}
	
	/**
	 * Filter the author URL
	 */
	public static function filter_author_url( $link, $author_id, $author_nicename ) {
		global $post;

		if ( ! $post ) {
			return $link;
		}

		// Check for v2.0 multiple authors first
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );

		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			// For the first author, return their URL
			$first_author = $multiple_authors[0];

			switch ( $first_author['type'] ?? '' ) {
				// v2.0: Team Member Author - link to team page
				case 'team_member':
					if ( ! empty( $first_author['team_member_id'] ) ) {
						$team_page_url = get_permalink( intval( $first_author['team_member_id'] ) );
						if ( $team_page_url ) {
							return $team_page_url;
						}
					}
					break;

				// v2.0: External Author (saved) - link to website if available
				case 'external_saved':
					if ( ! empty( $first_author['external_author_id'] ) ) {
						$external_authors = get_option( 'bw_schema_external_authors', array() );
						$ext_id = $first_author['external_author_id'];
						if ( isset( $external_authors[ $ext_id ] ) && ! empty( $external_authors[ $ext_id ]['website'] ) ) {
							return $external_authors[ $ext_id ]['website'];
						}
					}
					// No website - return original link
					return $link;

				// External (One-time) - link to website if available
				case 'external':
					if ( ! empty( $first_author['external']['website'] ) ) {
						return $first_author['external']['website'];
					}
					// No website - return original link
					return $link;

				// Legacy: Custom Author - link to team page or website
				case 'custom':
					if ( ! empty( $first_author['custom_author_id'] ) ) {
						$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );
						foreach ( $custom_authors_list as $custom_author ) {
							if ( $custom_author['id'] === $first_author['custom_author_id'] ) {
								// Determine author URL
								if ( ! empty( $custom_author['teamPageUrl'] ) ) {
									return $custom_author['teamPageUrl'];
								} elseif ( ! empty( $custom_author['teamPageId'] ) && $custom_author['teamPageId'] !== 'custom' ) {
									$team_url = get_permalink( $custom_author['teamPageId'] );
									if ( $team_url ) {
										return $team_url;
									}
								} elseif ( ! empty( $custom_author['social']['website'] ) ) {
									return $custom_author['social']['website'];
								}
								break;
							}
						}
					}
					// No URL found - return original link
					return $link;

				// Legacy: WordPress User - link to team page if linked, else author archive
				case 'wordpress':
					if ( ! empty( $first_author['wordpress_user_id'] ) ) {
						$linked_team = self::get_team_member_for_user( $first_author['wordpress_user_id'] );
						if ( $linked_team ) {
							return get_permalink( $linked_team );
						}
						return get_author_posts_url( $first_author['wordpress_user_id'] );
					}
					return $link;
			}
		}

		// Check if the WP author has a linked team member
		$linked_team_member = self::get_team_member_for_user( $author_id );
		if ( $linked_team_member ) {
			return get_permalink( $linked_team_member );
		}

		// Legacy: Check custom author
		$custom_author = self::get_custom_author();

		if ( $custom_author ) {
			// Determine author URL with same logic as author box
			$author_url = '';

			// First check for custom team page URL
			if ( ! empty( $custom_author['teamPageUrl'] ) ) {
				$author_url = $custom_author['teamPageUrl'];
			}
			// Then check for internal team page
			elseif ( ! empty( $custom_author['teamPageId'] ) && $custom_author['teamPageId'] !== 'custom' ) {
				$author_url = get_permalink( $custom_author['teamPageId'] );
			}
			// Finally fall back to website from social profiles
			elseif ( ! empty( $custom_author['social']['website'] ) ) {
				$author_url = $custom_author['social']['website'];
			}

			if ( $author_url ) {
				return $author_url;
			}
		}

		return $link;
	}
	
	/**
	 * Filter author meta for social links
	 */
	public static function filter_author_meta( $value, $field, $user_id ) {
		// Only filter on single posts
		if ( ! is_singular() ) {
			return $value;
		}
		
		$custom_author = self::get_custom_author();
		
		if ( ! $custom_author ) {
			return $value;
		}
		
		// Map common social fields
		$social_map = array(
			'facebook' => 'facebook',
			'twitter' => 'twitter',
			'instagram' => 'instagram',
			'linkedin' => 'linkedin',
			'youtube' => 'youtube',
			'user_url' => 'website',
		);
		
		if ( isset( $social_map[$field] ) && ! empty( $custom_author['social'][$social_map[$field]] ) ) {
			return $custom_author['social'][$social_map[$field]];
		}
		
		// Handle job title
		if ( $field === 'job_title' && ! empty( $custom_author['jobTitle'] ) ) {
			return $custom_author['jobTitle'];
		}
		
		// Handle display name
		if ( $field === 'display_name' && ! empty( $custom_author['name'] ) ) {
			return $custom_author['name'];
		}
		
		// Handle Kadence theme specific meta fields
		if ( $field === 'kadence_facebook_url' && ! empty( $custom_author['social']['facebook'] ) ) {
			return $custom_author['social']['facebook'];
		}
		if ( $field === 'kadence_twitter_url' && ! empty( $custom_author['social']['twitter'] ) ) {
			return $custom_author['social']['twitter'];
		}
		if ( $field === 'kadence_linkedin_url' && ! empty( $custom_author['social']['linkedin'] ) ) {
			return $custom_author['social']['linkedin'];
		}
		if ( $field === 'kadence_instagram_url' && ! empty( $custom_author['social']['instagram'] ) ) {
			return $custom_author['social']['instagram'];
		}
		if ( $field === 'kadence_youtube_url' && ! empty( $custom_author['social']['youtube'] ) ) {
			return $custom_author['social']['youtube'];
		}
		
		return $value;
	}
	
	/**
	 * Filter Kadence theme author data
	 */
	public static function filter_kadence_author_data( $author_data, $author_id ) {
		$custom_author = self::get_custom_author();
		
		if ( ! $custom_author ) {
			return $author_data;
		}
		
		// Override author data with custom author
		$author_data['name'] = $custom_author['name'];
		$author_data['description'] = ! empty( $custom_author['description'] ) ? $custom_author['description'] : '';
		$author_data['avatar'] = ! empty( $custom_author['image'] ) ? $custom_author['image'] : '';
		
		// Social links
		if ( ! empty( $custom_author['social'] ) ) {
			$author_data['facebook'] = ! empty( $custom_author['social']['facebook'] ) ? $custom_author['social']['facebook'] : '';
			$author_data['twitter'] = ! empty( $custom_author['social']['twitter'] ) ? $custom_author['social']['twitter'] : '';
			$author_data['linkedin'] = ! empty( $custom_author['social']['linkedin'] ) ? $custom_author['social']['linkedin'] : '';
			$author_data['instagram'] = ! empty( $custom_author['social']['instagram'] ) ? $custom_author['social']['instagram'] : '';
			$author_data['youtube'] = ! empty( $custom_author['social']['youtube'] ) ? $custom_author['social']['youtube'] : '';
			$author_data['website'] = ! empty( $custom_author['social']['website'] ) ? $custom_author['social']['website'] : '';
		}
		
		return $author_data;
	}
	
	/**
	 * Filter Kadence theme social links
	 */
	public static function filter_kadence_social_links( $social_links, $author_id ) {
		$custom_author = self::get_custom_author();
		
		if ( ! $custom_author || empty( $custom_author['social'] ) ) {
			return $social_links;
		}
		
		// Override social links
		$new_links = array();
		
		if ( ! empty( $custom_author['social']['facebook'] ) ) {
			$new_links['facebook'] = $custom_author['social']['facebook'];
		}
		if ( ! empty( $custom_author['social']['twitter'] ) ) {
			$new_links['twitter'] = $custom_author['social']['twitter'];
		}
		if ( ! empty( $custom_author['social']['linkedin'] ) ) {
			$new_links['linkedin'] = $custom_author['social']['linkedin'];
		}
		if ( ! empty( $custom_author['social']['instagram'] ) ) {
			$new_links['instagram'] = $custom_author['social']['instagram'];
		}
		if ( ! empty( $custom_author['social']['youtube'] ) ) {
			$new_links['youtube'] = $custom_author['social']['youtube'];
		}
		
		return ! empty( $new_links ) ? $new_links : $social_links;
	}
	
	/**
	 * Filter Yoast Person schema
	 */
	public static function filter_yoast_person_schema( $person_data, $context, $graph_piece ) {
		$custom_author = self::get_custom_author();
		
		if ( ! $custom_author ) {
			return $person_data;
		}
		
		// Override person data with custom author
		$person_data['name'] = $custom_author['name'];
		$person_data['description'] = ! empty( $custom_author['description'] ) ? $custom_author['description'] : '';
		
		if ( ! empty( $custom_author['image'] ) ) {
			$person_data['image'] = array(
				'@type' => 'ImageObject',
				'@id' => $person_data['@id'] . '#image',
				'url' => $custom_author['image'],
				'contentUrl' => $custom_author['image'],
				'caption' => $custom_author['name']
			);
		}
		
		// Update sameAs with social profiles
		$same_as = array();
		if ( ! empty( $custom_author['social'] ) ) {
			foreach ( $custom_author['social'] as $platform => $url ) {
				if ( ! empty( $url ) && $url !== '#' ) {
					$same_as[] = $url;
				}
			}
		}
		if ( ! empty( $same_as ) ) {
			$person_data['sameAs'] = $same_as;
		}
		
		// Add job title if available
		if ( ! empty( $custom_author['jobTitle'] ) ) {
			$person_data['jobTitle'] = $custom_author['jobTitle'];
		}
		
		return $person_data;
	}
	
	/**
	 * Filter Yoast Article schema
	 */
	public static function filter_yoast_article_schema( $article_data, $context, $graph_piece ) {
		$custom_author = self::get_custom_author();
		
		if ( ! $custom_author ) {
			return $article_data;
		}
		
		// Update author reference
		if ( isset( $article_data['author'] ) ) {
			$article_data['author']['name'] = $custom_author['name'];
		}
		
		return $article_data;
	}
	
	/**
	 * Filter Kadence author output
	 */
	public static function filter_kadence_author_output( $output, $post_id = null ) {
		global $post;
		
		if ( ! $post_id && $post ) {
			$post_id = $post->ID;
		}
		
		if ( ! $post_id ) {
			return $output;
		}
		
		// Get all authors for this post
		$multiple_authors = get_post_meta( $post_id, '_bw_schema_multiple_authors', true );
		
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );
			
			foreach ( $multiple_authors as $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}

				if ( $name ) {
					$author_names[] = $name;
				}
			}

			if ( ! empty( $author_names ) ) {
				// Generate linked author names for byline display
				$formatted_names_with_links = self::format_author_names( $author_names, true, $multiple_authors );

				// Replace the author vcard content with linked names
				// Pattern 1: Replace entire author vcard contents
				$output = preg_replace(
					'/(<span[^>]*class="[^"]*author\s+vcard[^"]*"[^>]*>).*?(<\/span>)/is',
					'$1' . $formatted_names_with_links . '$2',
					$output
				);
			}
		}
		
		return $output;
	}
	
	/**
	 * Start output buffer to capture author display
	 */
	public static function start_author_buffer() {
		if ( is_singular() && ! is_admin() ) {
			ob_start( array( __CLASS__, 'filter_author_in_buffer' ) );
		}
	}
	
	/**
	 * End output buffer and filter author display
	 */
	public static function end_author_buffer() {
		if ( is_singular() && ! is_admin() && ob_get_level() ) {
			ob_end_flush();
		}
	}
	
	/**
	 * Filter author display in buffer
	 */
	public static function filter_author_in_buffer( $buffer ) {
		global $post;
		
		if ( ! $post ) {
			return $buffer;
		}
		
		// Get all authors for this post
		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );
		
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) && count( $multiple_authors ) > 1 ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );
			
			foreach ( $multiple_authors as $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}

				if ( $name ) {
					$author_names[] = $name;
				}
			}

			if ( count( $author_names ) > 1 ) {
				$formatted_names_with_links = self::format_author_names( $author_names, true, $multiple_authors );
				$formatted_names_text = self::format_author_names( $author_names );

				// Get the first author name to replace
				$first_author_name = $author_names[0];

				// Kadence specific pattern - replace the entire author vcard content
				$kadence_pattern = '/(<span[^>]*class="[^"]*posted-by[^"]*"[^>]*>.*?<span[^>]*class="[^"]*author\s+vcard[^"]*"[^>]*>)[^<]*(?:<a[^>]*>[^<]+<\/a>)?([^<]*<\/span>)/is';
				$buffer = preg_replace( $kadence_pattern, '$1' . $formatted_names_with_links . '$2', $buffer );
				
				// Alternative pattern if the above doesn't work
				$alt_pattern = '/(<span[^>]*class="[^"]*author\s+vcard[^"]*"[^>]*>).*?(<\/span>)/is';
				$buffer = preg_replace( $alt_pattern, '$1' . $formatted_names_with_links . '$2', $buffer );
			}
		}
		
		return $buffer;
	}
	
	/**
	 * Output JavaScript to update author display
	 * This is a fallback for themes that don't use standard WordPress author filters
	 */
	public static function output_author_js() {
		if ( ! is_singular() || is_admin() ) {
			return;
		}

		global $post;
		if ( ! $post ) {
			return;
		}

		$multiple_authors = get_post_meta( $post->ID, '_bw_schema_multiple_authors', true );

		// Run JS fallback if there are ANY assigned authors (single or multiple)
		if ( ! empty( $multiple_authors ) && is_array( $multiple_authors ) ) {
			$author_names = array();
			$custom_authors_list = get_option( 'bw_schema_custom_authors', array() );
			$has_custom_or_external = false;

			foreach ( $multiple_authors as $author_data ) {
				$name = null;

				switch ( $author_data['type'] ) {
					// v2.0: Team Member Author
					case 'team_member':
						$has_custom_or_external = true; // Treat team members like custom authors
						if ( ! empty( $author_data['team_member_id'] ) ) {
							$team_member = get_post( intval( $author_data['team_member_id'] ) );
							if ( $team_member && $team_member->post_status === 'publish' ) {
								$name = $team_member->post_title;
							}
						}
						break;

					// v2.0: External Author (saved in settings)
					case 'external_saved':
						$has_custom_or_external = true;
						if ( ! empty( $author_data['external_author_id'] ) ) {
							$external_authors = get_option( 'bw_schema_external_authors', array() );
							$author_id = $author_data['external_author_id'];
							if ( isset( $external_authors[ $author_id ] ) ) {
								$name = $external_authors[ $author_id ]['name'] ?? '';
							}
						}
						break;

					// Legacy: WordPress User
					case 'wordpress':
						if ( ! empty( $author_data['wordpress_user_id'] ) ) {
							$user = get_userdata( $author_data['wordpress_user_id'] );
							if ( $user ) {
								$name = $user->display_name;
							}
						}
						break;

					// Legacy: Custom Author
					case 'custom':
						$has_custom_or_external = true;
						if ( ! empty( $author_data['custom_author_id'] ) ) {
							foreach ( $custom_authors_list as $custom_author ) {
								if ( $custom_author['id'] === $author_data['custom_author_id'] ) {
									$name = $custom_author['name'];
									break;
								}
							}
						}
						break;

					// External (One-time)
					case 'external':
						$has_custom_or_external = true;
						if ( ! empty( $author_data['external']['name'] ) ) {
							$name = $author_data['external']['name'];
						}
						break;
				}

				if ( $name ) {
					$author_names[] = $name;
				}
			}

			// Only output JS if we have custom/external/team member authors OR multiple authors
			// (WordPress-only single author doesn't need override)
			if ( ! empty( $author_names ) && ( $has_custom_or_external || count( $author_names ) > 1 ) ) {
				$formatted_names = self::format_author_names( $author_names );
				$first_author_name = $author_names[0];
				// Get the WordPress post author name for matching
				$wp_author = get_userdata( $post->post_author );
				$wp_author_name = $wp_author ? $wp_author->display_name : '';
				?>
				<script>
				(function() {
					document.addEventListener('DOMContentLoaded', function() {
						// Update author displays
						var authorElements = document.querySelectorAll('.entry-author-name, .author-name, .by-author, .post-author, a[rel="author"], .author.vcard a, .author.vcard span.fn');
						var firstAuthorName = <?php echo json_encode( $first_author_name ); ?>;
						var wpAuthorName = <?php echo json_encode( $wp_author_name ); ?>;
						var formattedNames = <?php echo json_encode( $formatted_names ); ?>;
						// Update all author displays
						authorElements.forEach(function(element) {
							var text = element.textContent.trim();

							// Check if this element contains author names (custom author name, WP author name, or multiple author patterns)
							if (text === wpAuthorName || text.includes(firstAuthorName) || text.includes(wpAuthorName) || text.includes(' and ') || text.includes('others')) {
								element.textContent = formattedNames;
							}
						});
						
						// Special handling for Kadence theme structure
						var kadenceVcard = document.querySelector('.posted-by .author.vcard');
						if (kadenceVcard) {
							// Find the actual text node or span containing the author names
							var authorTextElement = kadenceVcard.querySelector('a, span.fn');
							if (authorTextElement) {
								authorTextElement.textContent = formattedNames;
							}
						}
						
						// Handle cases where "and X others" might be in separate text nodes
						document.querySelectorAll('.posted-by').forEach(function(container) {
							var walker = document.createTreeWalker(
								container,
								NodeFilter.SHOW_TEXT,
								null,
								false
							);
							
							var node;
							while (node = walker.nextNode()) {
								if (node.textContent.includes(' and ') && node.textContent.includes('other')) {
									// This is likely our "and X others" text
									var parent = node.parentElement;
									if (parent && parent.classList.contains('vcard')) {
										// Replace the entire author vcard content
										parent.textContent = formattedNames;
									}
								}
							}
						});
					});
				})();
				</script>
				<?php
			}
		}
	}
}