<?php
/**
 * OpenAlex API access: author lookup and works fetching.
 */

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

/**
 * GET a JSON endpoint. Returns the decoded array, or false on any failure.
 */
function publiton_openalex_get( $url ) {
	$response = wp_remote_get( $url, array( 'timeout' => 20 ) );
	if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
		return false;
	}
	$data = json_decode( wp_remote_retrieve_body( $response ), true );
	return is_array( $data ) ? $data : false;
}

function publiton_mailto() {
	$settings = publiton_get_settings();
	$mailto   = ! empty( $settings['mailto'] ) ? $settings['mailto'] : get_bloginfo( 'admin_email' );
	return rawurlencode( $mailto );
}

/**
 * Resolve user input (ORCID iD, OpenAlex ID, or a name) to author candidates.
 *
 * @return array|false List of candidates (possibly empty), or false when OpenAlex is unreachable.
 */
function publiton_lookup_author( $query ) {
	$query = trim( $query );
	$query = preg_replace( '~^https?://(www\.)?orcid\.org/~i', '', $query );
	$query = preg_replace( '~^https?://openalex\.org/(authors/)?~i', '', $query );

	if ( preg_match( '/^\d{4}-\d{4}-\d{4}-\d{3}[\dX]$/i', $query ) ) {
		$data = publiton_openalex_get( 'https://api.openalex.org/authors/orcid:' . rawurlencode( strtoupper( $query ) ) . '?mailto=' . publiton_mailto() );
		if ( false === $data ) {
			return false;
		}
		return empty( $data['id'] ) ? array() : array( publiton_author_candidate( $data ) );
	}

	if ( preg_match( '/^A\d{4,}$/i', $query ) ) {
		$data = publiton_openalex_get( 'https://api.openalex.org/authors/' . rawurlencode( strtoupper( $query ) ) . '?mailto=' . publiton_mailto() );
		if ( false === $data ) {
			return false;
		}
		return empty( $data['id'] ) ? array() : array( publiton_author_candidate( $data ) );
	}

	$data = publiton_openalex_get( 'https://api.openalex.org/authors?search=' . rawurlencode( $query ) . '&per-page=5&mailto=' . publiton_mailto() );
	if ( false === $data ) {
		return false;
	}
	$candidates = array();
	if ( ! empty( $data['results'] ) ) {
		foreach ( $data['results'] as $author ) {
			$candidates[] = publiton_author_candidate( $author );
		}
	}
	return $candidates;
}

function publiton_author_candidate( $author ) {
	$institution = '';
	if ( ! empty( $author['last_known_institutions'][0]['display_name'] ) ) {
		$institution = $author['last_known_institutions'][0]['display_name'];
	}
	return array(
		'id'    => str_replace( 'https://openalex.org/', '', $author['id'] ),
		'name'  => isset( $author['display_name'] ) ? $author['display_name'] : '',
		'hint'  => $institution,
		'works' => isset( $author['works_count'] ) ? (int) $author['works_count'] : 0,
	);
}

/**
 * Parse the year-overrides textarea: lines of "2026 | Exact paper title".
 *
 * @return array lowercase title => year
 */
function publiton_parse_year_overrides( $raw ) {
	$map = array();
	foreach ( preg_split( '/\r\n|\r|\n/', (string) $raw ) as $line ) {
		if ( preg_match( '/^\s*(\d{4})\s*\|\s*(.+?)\s*$/', $line, $m ) ) {
			$map[ strtolower( $m[2] ) ] = (int) $m[1];
		}
	}
	return $map;
}

/**
 * Parse the excluded-titles textarea: one title per line.
 *
 * @return array lowercase title => true
 */
function publiton_parse_excluded_titles( $raw ) {
	$map = array();
	foreach ( preg_split( '/\r\n|\r|\n/', (string) $raw ) as $line ) {
		$line = strtolower( trim( $line ) );
		if ( '' !== $line ) {
			$map[ $line ] = true;
		}
	}
	return $map;
}

/**
 * Parse the extra-author-IDs textarea (Pro): one OpenAlex author ID per line.
 *
 * @return array Uppercased, validated A#### IDs.
 */
function publiton_parse_extra_author_ids( $raw ) {
	$ids = array();
	foreach ( preg_split( '/\r\n|\r|\n/', (string) $raw ) as $line ) {
		$line = strtoupper( trim( $line ) );
		$line = preg_replace( '~^HTTPS?://OPENALEX\.ORG/(AUTHORS/)?~', '', $line );
		if ( preg_match( '/^A\d{4,}$/', $line ) ) {
			$ids[] = $line;
		}
	}
	return array_values( array_unique( $ids ) );
}

/**
 * The full list of OpenAlex author IDs to include: the primary author, plus any
 * extra IDs when Pro is active (used for research-group / merged-profile mode).
 *
 * @return array
 */
function publiton_author_ids( $settings, $pro ) {
	$ids = array();
	if ( '' !== $settings['author_id'] ) {
		$ids[] = $settings['author_id'];
	}
	if ( $pro ) {
		foreach ( publiton_parse_extra_author_ids( $settings['extra_author_ids'] ) as $id ) {
			$ids[] = $id;
		}
	}
	return array_values( array_unique( $ids ) );
}

/**
 * Fetch every raw OpenAlex work for a single author ID (cursor-paginated).
 *
 * @return array|false Raw work objects, or false when OpenAlex is unreachable.
 */
function publiton_fetch_raw_works( $author_id ) {
	$raw    = array();
	$cursor = '*';
	$guard  = 0;
	while ( $cursor && $guard < 20 ) {
		$guard++;
		$url  = 'https://api.openalex.org/works?filter=' . rawurlencode( 'author.id:' . $author_id )
			. '&per-page=200&sort=' . rawurlencode( 'publication_date:desc' )
			. '&cursor=' . rawurlencode( $cursor )
			. '&mailto=' . publiton_mailto();
		$data = publiton_openalex_get( $url );
		if ( false === $data || ! isset( $data['results'] ) ) {
			return false; // a partial list is worse than the cached fallback
		}
		$raw    = array_merge( $raw, $data['results'] );
		$cursor = isset( $data['meta']['next_cursor'] ) ? $data['meta']['next_cursor'] : null;
		if ( count( $data['results'] ) < 200 ) {
			break;
		}
	}
	return $raw;
}

/**
 * Fetch and normalize all works for the configured author(s).
 *
 * Each work: pub_id, year, date, title, authors (list of ['name' =>, 'you' =>]),
 * venue, link, citations, counts_by_year (year => count).
 *
 * @param array $settings Plugin settings.
 * @param bool  $pro      Whether Pro features are active (extra author IDs).
 * @return array|false
 */
function publiton_fetch_works( $settings, $pro = false ) {
	$author_ids = publiton_author_ids( $settings, $pro );
	if ( empty( $author_ids ) ) {
		return false;
	}

	$raw_results = array();
	foreach ( $author_ids as $author_id ) {
		$raw = publiton_fetch_raw_works( $author_id );
		if ( false === $raw ) {
			return false;
		}
		$raw_results = array_merge( $raw_results, $raw );
	}

	$excluded_types  = (array) $settings['excluded_types'];
	$excluded_titles = publiton_parse_excluded_titles( $settings['excluded_titles'] );
	$year_overrides  = publiton_parse_year_overrides( $settings['year_overrides'] );
	$author_urls     = array();
	foreach ( $author_ids as $author_id ) {
		$author_urls[ 'https://openalex.org/' . $author_id ] = true;
	}

	$works       = array();
	$seen_titles = array();
	$seen_ids    = array();
	foreach ( (array) $settings['manual_entries'] as $manual ) {
		$seen_titles[ strtolower( trim( $manual['title'] ) ) ] = true;
	}

	foreach ( $raw_results as $work ) {
		if ( empty( $work['title'] ) || empty( $work['publication_year'] ) ) {
			continue;
		}
		if ( in_array( $work['type'], $excluded_types, true ) ) {
			continue;
		}
		// Dedup across authors: the same co-authored paper appears under each ID.
		$work_id = isset( $work['id'] ) ? $work['id'] : '';
		if ( '' !== $work_id && isset( $seen_ids[ $work_id ] ) ) {
			continue;
		}
		$title_key = strtolower( trim( $work['title'] ) );
		if ( isset( $seen_titles[ $title_key ] ) || isset( $excluded_titles[ $title_key ] ) ) {
			continue; // duplicate (e.g. dataset + article with the same title) or hidden
		}
		$seen_titles[ $title_key ] = true;
		if ( '' !== $work_id ) {
			$seen_ids[ $work_id ] = true;
		}

		$year = isset( $year_overrides[ $title_key ] ) ? $year_overrides[ $title_key ] : (int) $work['publication_year'];

		$authors = array();
		if ( ! empty( $work['authorships'] ) ) {
			foreach ( $work['authorships'] as $authorship ) {
				if ( ! empty( $authorship['author']['display_name'] ) ) {
					$authors[] = array(
						'name' => $authorship['author']['display_name'],
						'you'  => isset( $authorship['author']['id'] ) && isset( $author_urls[ $authorship['author']['id'] ] ),
					);
				}
			}
		}

		$venue = '';
		if ( ! empty( $work['primary_location']['source']['display_name'] ) ) {
			$venue = $work['primary_location']['source']['display_name'];
		}

		$link = '';
		if ( ! empty( $work['doi'] ) ) {
			$link = $work['doi']; // already a full https://doi.org/... url
		} elseif ( '' !== $work_id ) {
			$link = $work_id;
		}

		$counts_by_year = array();
		if ( ! empty( $work['counts_by_year'] ) ) {
			foreach ( $work['counts_by_year'] as $row ) {
				if ( isset( $row['year'], $row['cited_by_count'] ) ) {
					$counts_by_year[ (int) $row['year'] ] = (int) $row['cited_by_count'];
				}
			}
		}

		$works[] = array(
			'pub_id'         => $work_id,
			'year'           => $year,
			'date'           => isset( $work['publication_date'] ) ? $work['publication_date'] : $year . '-01-01',
			'title'          => $work['title'],
			'authors'        => $authors,
			'venue'          => $venue,
			'link'           => $link,
			'citations'      => isset( $work['cited_by_count'] ) ? (int) $work['cited_by_count'] : 0,
			'counts_by_year' => $counts_by_year,
		);
	}

	foreach ( (array) $settings['manual_entries'] as $manual ) {
		$authors = array();
		foreach ( array_filter( array_map( 'trim', explode( ',', (string) $manual['authors'] ) ) ) as $name ) {
			$authors[] = array(
				'name' => $name,
				'you'  => '' !== $settings['author_name'] && 0 === strcasecmp( $name, $settings['author_name'] ),
			);
		}
		$year    = (int) $manual['year'];
		$works[] = array(
			'pub_id'         => '',
			'year'           => $year,
			'date'           => ! empty( $manual['date'] ) ? $manual['date'] : $year . '-01-01',
			'title'          => $manual['title'],
			'authors'        => $authors,
			'venue'          => isset( $manual['venue'] ) ? $manual['venue'] : '',
			'link'           => isset( $manual['link'] ) ? $manual['link'] : '',
			'citations'      => 0,
			'counts_by_year' => array(),
		);
	}

	return $works;
}
