<?php
/**
 * Shared Lantern Span People Directory helper functions.
 *
 * @package LanternSpanPeopleDirectory
 */

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

/**
 * Returns the plugin settings with defaults applied.

 *
 * @return array<string, mixed>
 */
function lspd_get_settings() {
	$defaults = array(
		'directory_page_id' => 0,
		'access'            => 'logged_in',
		'per_page'          => 24,
		'sort_by'           => 'last_name',
		'show_alphabet'     => 1,
	);

	$settings = get_option( 'lspd_settings', array() );
	$settings = is_array( $settings ) ? $settings : array();

	$settings      = wp_parse_args( $settings, $defaults );
	$page_id       = is_scalar( $settings['directory_page_id'] ) ? absint( $settings['directory_page_id'] ) : 0;
	$access        = is_string( $settings['access'] ) ? sanitize_key( $settings['access'] ) : 'logged_in';
	$per_page      = is_scalar( $settings['per_page'] ) ? absint( $settings['per_page'] ) : 24;
	$sort_by       = is_string( $settings['sort_by'] ) ? sanitize_key( $settings['sort_by'] ) : 'last_name';
	$show_alphabet = is_scalar( $settings['show_alphabet'] ) ? absint( $settings['show_alphabet'] ) : 1;

	return array(
		'directory_page_id' => $page_id,
		'access'            => in_array( $access, array( 'logged_in', 'public' ), true ) ? $access : 'logged_in',
		'per_page'          => in_array( $per_page, array( 12, 24, 36, 48 ), true ) ? $per_page : 24,
		'sort_by'           => in_array( $sort_by, array( 'first_name', 'last_name' ), true ) ? $sort_by : 'last_name',
		'show_alphabet'     => $show_alphabet > 0 ? 1 : 0,
	);
}

/**
 * Stores sanitized directory settings in the permanent option.
 *
 * @param array<string, mixed> $settings Settings to store.
 * @return bool
 */
function lspd_update_settings( $settings ) {
	if ( ! is_array( $settings ) ) {
		return false;
	}

	return update_option( 'lspd_settings', $settings );
}

/**
 * Returns the configured directory page URL.
 *
 * @return string
 */
function lspd_get_directory_url() {
	$settings = lspd_get_settings();
	$page_id  = absint( $settings['directory_page_id'] );

	if ( $page_id > 0 && 'publish' === get_post_status( $page_id ) ) {
		$url = get_permalink( $page_id );

		if ( is_string( $url ) ) {
			return $url;
		}
	}

	return home_url( '/' );
}

/**
 * Returns the current page URL without directory navigation parameters.
 *
 * @return string
 */
function lspd_get_current_page_url() {
	$queried_id = get_queried_object_id();

	if ( $queried_id > 0 ) {
		$url = get_permalink( $queried_id );

		if ( is_string( $url ) ) {
			return $url;
		}
	}

	return lspd_get_directory_url();
}

/**
 * Returns the Lantern Span People Directory Pro upgrade URL.
 *
 * @return string
 */
function lspd_get_pro_url() {
	$default_url = defined( 'LSPD_PRO_URL' ) && is_string( LSPD_PRO_URL )
		? LSPD_PRO_URL
		: '';
	$freemius   = function_exists( 'lspd_get_freemius' ) ? lspd_get_freemius() : null;

	if ( is_object( $freemius ) ) {
		if ( is_callable( array( $freemius, 'is_paying_or_trial' ) ) && $freemius->is_paying_or_trial() ) {
			$default_url = '';
		} elseif ( is_callable( array( $freemius, 'get_upgrade_url' ) ) ) {
			$upgrade_url = $freemius->get_upgrade_url();

			if ( is_string( $upgrade_url ) && '' !== $upgrade_url ) {
				$default_url = $upgrade_url;
			}
		}
	}

	/**
	 * Filters the Lantern Span People Directory Pro URL.
	 *
	 * Return an empty string to hide upgrade links.
	 *
	 * @param string $url Pro product URL.
	 */
	$url = apply_filters( 'lspd_pro_url', $default_url );

	return is_string( $url ) ? esc_url_raw( $url ) : '';
}

/**
 * Gets a person's initials for the photo placeholder.
 *
 * @param string $name Person name.
 * @return string
 */
function lspd_get_initials( $name ) {
	$parts    = lspd_get_name_parts( $name );
	$selected = array();
	$initials = '';

	if ( '' !== $parts['first'] ) {
		$selected[] = $parts['first'];
	}

	if ( '' !== $parts['last'] && $parts['last'] !== $parts['first'] ) {
		$selected[] = $parts['last'];
	}

	if ( empty( $selected ) ) {
		return '';
	}

	foreach ( $selected as $word ) {
		if ( function_exists( 'mb_substr' ) ) {
			$initials .= mb_substr( $word, 0, 1, 'UTF-8' );
		} else {
			$initials .= substr( $word, 0, 1 );
		}
	}

	return function_exists( 'mb_strtoupper' )
		? mb_strtoupper( $initials, 'UTF-8' )
		: strtoupper( $initials );
}

/**
 * Splits a display name into practical first-name and last-name sort parts.
 *
 * Lantern Span People Directory intentionally keeps one simple display-name field. For sorting,
 * names entered as "Last, First" use the text before the comma as the last
 * name. Other names use the first meaningful word and the final meaningful
 * word, ignoring common titles and suffixes. The original display name is not
 * changed.
 *
 * @param mixed $name Display name.
 * @return array{first:string,last:string,full:string}
 */
function lspd_get_name_parts( $name ) {
	if ( ! is_string( $name ) ) {
		return array(
			'first' => '',
			'last'  => '',
			'full'  => '',
		);
	}

	$clean_name = trim( wp_strip_all_tags( $name ) );
	$clean_name = preg_replace( '/\s+/u', ' ', $clean_name );
	$clean_name = is_string( $clean_name ) ? trim( $clean_name ) : '';

	if ( '' === $clean_name ) {
		return array(
			'first' => '',
			'last'  => '',
			'full'  => '',
		);
	}

	$first = '';
	$last  = '';

	if ( false !== strpos( $clean_name, ',' ) ) {
		$name_sections    = explode( ',', $clean_name, 2 );
		$leading_section  = isset( $name_sections[0] ) ? trim( $name_sections[0] ) : '';
		$trailing_section = isset( $name_sections[1] ) ? trim( $name_sections[1] ) : '';

		if ( lspd_is_suffix_only_name_section( $trailing_section ) ) {
			// Treat common display forms such as "John Smith, Jr." as a normal
			// first-name/last-name entry rather than as "Last, First".
			$name_tokens = lspd_tokenize_name( $leading_section );
			$name_tokens = lspd_remove_name_titles( $name_tokens );
			$name_tokens = lspd_remove_name_suffixes( $name_tokens );

			if ( ! empty( $name_tokens ) ) {
				$first = lspd_trim_name_part( $name_tokens[0] );
				$last  = lspd_trim_name_part( $name_tokens[ count( $name_tokens ) - 1 ] );
			}
		} else {
			$last        = lspd_trim_name_part( $leading_section );
			$given_names = lspd_tokenize_name( $trailing_section );
			$given_names = lspd_remove_name_titles( $given_names );
			$given_names = lspd_remove_name_suffixes( $given_names );
			$first       = ! empty( $given_names ) ? lspd_trim_name_part( $given_names[0] ) : '';
		}
	} else {
		$name_tokens = lspd_tokenize_name( $clean_name );
		$name_tokens = lspd_remove_name_titles( $name_tokens );
		$name_tokens = lspd_remove_name_suffixes( $name_tokens );

		if ( ! empty( $name_tokens ) ) {
			$first = lspd_trim_name_part( $name_tokens[0] );
			$last  = lspd_trim_name_part( $name_tokens[ count( $name_tokens ) - 1 ] );
		}
	}

	if ( '' === $first && '' !== $last ) {
		$first = $last;
	}

	if ( '' === $last && '' !== $first ) {
		$last = $first;
	}

	if ( '' === $first && '' === $last ) {
		$first = $clean_name;
		$last  = $clean_name;
	}

	return array(
		'first' => $first,
		'last'  => $last,
		'full'  => $clean_name,
	);
}

/**
 * Returns a stable, accent-insensitive key used to sort a display name.
 *
 * @param mixed  $name    Display name.
 * @param string $sort_by first_name or last_name.
 * @return string
 */
function lspd_get_name_sort_key( $name, $sort_by ) {
	$parts   = lspd_get_name_parts( $name );
	$sort_by = 'first_name' === $sort_by ? 'first_name' : 'last_name';
	$first   = lspd_normalize_sort_text( $parts['first'] );
	$last    = lspd_normalize_sort_text( $parts['last'] );
	$full    = lspd_normalize_sort_text( $parts['full'] );

	return 'first_name' === $sort_by
		? $first . "\n" . $last . "\n" . $full
		: $last . "\n" . $first . "\n" . $full;
}

/**
 * Compares two display names by the selected directory order.
 *
 * Each name component is compared separately. This avoids delimiter side
 * effects such as sorting "Alexander" before the shorter name "Alex".
 *
 * @param mixed  $first_name  First display name.
 * @param mixed  $second_name Second display name.
 * @param string $sort_by     first_name or last_name.
 * @return int
 */
function lspd_compare_names( $first_name, $second_name, $sort_by ) {
	$first_parts  = lspd_get_name_parts( $first_name );
	$second_parts = lspd_get_name_parts( $second_name );
	$sort_by      = 'first_name' === $sort_by ? 'first_name' : 'last_name';

	if ( 'first_name' === $sort_by ) {
		$first_values = array( $first_parts['first'], $first_parts['last'], $first_parts['full'] );
		$second_values = array( $second_parts['first'], $second_parts['last'], $second_parts['full'] );
	} else {
		$first_values = array( $first_parts['last'], $first_parts['first'], $first_parts['full'] );
		$second_values = array( $second_parts['last'], $second_parts['first'], $second_parts['full'] );
	}

	foreach ( $first_values as $index => $first_value ) {
		$comparison = strnatcasecmp(
			lspd_normalize_sort_text( $first_value ),
			lspd_normalize_sort_text( $second_values[ $index ] )
		);

		if ( 0 !== $comparison ) {
			return $comparison;
		}
	}

	return 0;
}

/**
 * Returns the A-Z directory section for a display name.
 *
 * Names whose selected sort part does not begin with A-Z use the # section.
 * Accented Latin letters are folded to their unaccented equivalent when the
 * WordPress remove_accents() helper is available.
 *
 * @param mixed  $name    Display name.
 * @param string $sort_by first_name or last_name.
 * @return string
 */
function lspd_get_name_initial( $name, $sort_by ) {
	$parts      = lspd_get_name_parts( $name );
	$name_part  = 'first_name' === $sort_by ? $parts['first'] : $parts['last'];
	$normalized = lspd_normalize_sort_text( $name_part );

	if ( '' === $normalized ) {
		return '#';
	}

	$initial = function_exists( 'mb_substr' )
		? mb_substr( $normalized, 0, 1, 'UTF-8' )
		: substr( $normalized, 0, 1 );
	$initial = strtoupper( $initial );

	return 1 === preg_match( '/^[A-Z]$/', $initial ) ? $initial : '#';
}

/**
 * Normalizes text for directory sorting.
 *
 * @param mixed $text Text to normalize.
 * @return string
 */
function lspd_normalize_sort_text( $text ) {
	if ( ! is_string( $text ) ) {
		return '';
	}

	$text = trim( wp_strip_all_tags( $text ) );

	if ( function_exists( 'remove_accents' ) ) {
		$text = remove_accents( $text );
	}

	$text = function_exists( 'mb_strtolower' )
		? mb_strtolower( $text, 'UTF-8' )
		: strtolower( $text );

	$text = preg_replace( '/[^a-z0-9]+/u', ' ', $text );

	return is_string( $text ) ? trim( $text ) : '';
}

/**
 * Tokenizes a name into whitespace-separated words.
 *
 * @param string $name Name or name section.
 * @return array<int, string>
 */
function lspd_tokenize_name( $name ) {
	$tokens = preg_split( '/\s+/u', trim( $name ) );

	if ( ! is_array( $tokens ) ) {
		return array();
	}

	return array_values(
		array_filter(
			$tokens,
			static function ( $token ) {
				return is_string( $token ) && '' !== trim( $token );
			}
		)
	);
}

/**
 * Removes common honorifics from the beginning of a tokenized name.
 *
 * @param array<int, string> $tokens Name tokens.
 * @return array<int, string>
 */
function lspd_remove_name_titles( $tokens ) {
	$titles = array(
		'mr',
		'mrs',
		'ms',
		'miss',
		'dr',
		'rev',
		'reverend',
		'pastor',
		'elder',
		'bishop',
		'deacon',
		'rabbi',
		'prof',
		'professor',
		'hon',
		'honorable',
		'fr',
		'father',
	);

	while ( ! empty( $tokens ) ) {
		$candidate = lspd_normalize_name_token( $tokens[0] );

		if ( ! in_array( $candidate, $titles, true ) ) {
			break;
		}

		array_shift( $tokens );
	}

	return array_values( $tokens );
}

/**
 * Removes common suffixes from the end of a tokenized name.
 *
 * @param array<int, string> $tokens Name tokens.
 * @return array<int, string>
 */
function lspd_remove_name_suffixes( $tokens ) {
	$suffixes = lspd_get_name_suffix_tokens();

	while ( count( $tokens ) > 1 ) {
		$last_index = count( $tokens ) - 1;
		$candidate  = lspd_normalize_name_token( $tokens[ $last_index ] );

		if ( ! in_array( $candidate, $suffixes, true ) ) {
			break;
		}

		array_pop( $tokens );
	}

	return array_values( $tokens );
}

/**
 * Determines whether text after a comma contains only name suffixes or
 * professional credentials.
 *
 * @param mixed $section Trailing comma-separated name section.
 * @return bool
 */
function lspd_is_suffix_only_name_section( $section ) {
	if ( ! is_string( $section ) || '' === trim( $section ) ) {
		return false;
	}

	$tokens   = lspd_tokenize_name( str_replace( ',', ' ', $section ) );
	$suffixes = lspd_get_name_suffix_tokens();

	if ( empty( $tokens ) ) {
		return false;
	}

	foreach ( $tokens as $token ) {
		if ( ! in_array( lspd_normalize_name_token( $token ), $suffixes, true ) ) {
			return false;
		}
	}

	return true;
}

/**
 * Returns common suffixes and credentials ignored for directory sorting.
 *
 * @return array<int, string>
 */
function lspd_get_name_suffix_tokens() {
	return array(
		'jr',
		'sr',
		'ii',
		'iii',
		'iv',
		'v',
		'vi',
		'esq',
		'jd',
		'phd',
		'md',
		'dds',
		'dvm',
		'rn',
		'cpa',
		'ret',
	);
}

/**
 * Normalizes a single title or suffix token for comparison.
 *
 * @param mixed $token Token.
 * @return string
 */
function lspd_normalize_name_token( $token ) {
	if ( ! is_string( $token ) ) {
		return '';
	}

	$token = trim( $token, " \t\n\r\0\x0B.," );
	$token = function_exists( 'mb_strtolower' )
		? mb_strtolower( $token, 'UTF-8' )
		: strtolower( $token );

	$token = preg_replace( '/[^a-z0-9]/', '', $token );

	return is_string( $token ) ? $token : '';
}

/**
 * Trims punctuation that is commonly attached to a name part.
 *
 * @param mixed $part Name part.
 * @return string
 */
function lspd_trim_name_part( $part ) {
	return is_string( $part ) ? trim( $part, " \t\n\r\0\x0B,." ) : '';
}
