<?php
/**
 * Matador / REST API / Jobs Namespace / Search Endpoint
 *
 * @link        https://matadorjobs.com/
 * @since       3.9.0
 *
 * @package     Matador Jobs Board
 * @subpackage  REST API
 * @author      Matador Software LLC, Jeremy Scott, Paul Bearne
 * @copyright   (c) 2024, Matador Software, LLC
 *
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 */

namespace matador\MatadorJobs\Rest\Endpoint\Jobs;

use matador\Job_Taxonomies;
use WP_Error;
use WP_Query;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

use matador\Matador;
use matador\Template_Support;
use matador\MatadorJobs\Core\Logger;
use matador\MatadorJobs\Core\Severity;
use matador\MatadorJobs\Rest\Endpoint\EndpointAbstract;
use matador\MatadorJobs\Rest\Schema\JobListings as Schema;

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

/**
 * Job Taxonomies Endpoint Class
 *
 * @since 3.9.0
 */
class JobListings extends EndpointAbstract {

	/**
	 * Class Name
	 *
	 * @var string
	 *
	 * @since 3.9.0
	 */
	protected static $class = __CLASS__;

	/**
	 * Routes
	 *
	 * Called in a WordPress rest_api_init action to add the routes.
	 *
	 * @since 3.9.0
	 *
	 * @return void
	 */
	public static function routes(): void {

		// INDEX
		register_rest_route( self::rest_namespace(), '/jobs/job-listings', [
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => [ __CLASS__, 'index' ],
			'permission_callback' => '__return_true',
			'args'                => Schema::index(),
		] );

		// SHOW
		register_rest_route( self::rest_namespace(), '/jobs/job-listings/(?P<id>[a-zA-Z0-9-_^/]+)', [
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => [ __CLASS__, 'show' ],
			'permission_callback' => '__return_true',
			'args'                => Schema::show(),
		] );

		// DISPLAY FIELDS
		register_rest_route( self::rest_namespace(), 'jobs/job-fields/', [
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => [ __CLASS__, 'display_fields' ],
			'permission_callback' => '__return_true', // @todo - consider making this route accessible only when logged in as it's only for retrieving data for page builders
			'args'                => Schema::display_fields(),
		] );
	}

	/**
	 * Index
	 *
	 * Returns a list of Jobs for the Matador Jobs post type.
	 *
	 * @since 3.9.0
	 *
	 * @param WP_REST_Request $request
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public static function index( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$jobs = [];

		$args = [
			'post_type' => Matador::variable( 'post_type_key_job_listing' ),
		];

		if ( ! empty( $request['s'] ) ) {
			$args['s'] = $request['s'];
		}

		$query = new WP_Query( $args );

		if ( $query->have_posts() ) {
			$fields = self::parse_job_fields( $request['fields'] );

			while ( $query->have_posts() ) {
				$query->the_post();

				$job_data = [];

				foreach ( $fields as $field ) {
					$field_value = self::retrieve_field_value( $field, $request );

					if ( ! empty( $field_value ) ) {

						$job_data[ $field ] = $field_value;
					}
				}

				if ( ! empty( $job_data ) ) {

					$jobs[] = $job_data;
				}
			}
			wp_reset_postdata();
		}

		return new WP_REST_Response( $jobs, 200 );
	}

	/**
	 * Show
	 *
	 * @since 3.9.0
	 *
	 * @param  WP_REST_Request $request
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public static function show( WP_REST_Request $request ): WP_REST_Response|WP_Error {

		$job_id = $request['id'];

		if ( empty( $job_id ) ) {
			$code    = 'rest-jobs-job-listings-show-missing-id';
			$message = __( 'No job ID detected, please provide the WordPress ID of a job listing', 'matador-jobs' );
			Logger::entry( $code, $message, Severity::ERROR );
			return new WP_Error( $code, $message, [ 'status' => 422 ] );
		}

		$raw_job = get_post( $job_id );

		if ( empty( $raw_job ) || Matador::variable( 'post_type_key_job_listing' ) !== $raw_job->post_type ) {
			$code    = 'rest-jobs-job-listings-show-no-job-found';
			$message = __( "No Job was found with a WordPress ID of $job_id", 'matador-jobs' );
			Logger::entry( $code, $message, Severity::NOTE );
			return new WP_REST_Response( [ 'message' => __( "No job found with ID $job_id" )  ], 404 );
		}

		$job    = [];
		$fields = self::parse_job_fields( $request['fields'] );

		foreach ( $fields as $field ) {

			$field_value = self::retrieve_field_value( $field, $request, $job_id );

			if ( ! empty( $field_value ) ) {
				$job[ $field ] = $field_value;
			}
		}

		return new WP_REST_Response( $job, 200 );
	}

	/**
	 * Parse Job Fields - transform comma separated string of fields to array and validate resulting fields.
	 *
	 * @since 3.9.0
	 *
	 * @param  string $fields_param - comma separated string of fields e.g. 'id,title,description'.
	 *
	 * @return array
	 */
	private static function parse_job_fields( string $fields_param ): array {
		return array_map( 'trim', explode( ',', $fields_param ) );
	}

	/**
	 * Retrieve Field Value
	 *
	 * @since 3.9.0
	 *
	 * @param string          $field - name of the field.
	 * @param WP_REST_Request $request - the request received.
	 * @param int             $id - WP ID of the job.
	 *
	 * @return mixed
	 */
	private static function retrieve_field_value( string $field, WP_REST_Request $request, int $id = null ): mixed {

		$id = $id ?? get_the_ID();

		if ( empty( $id ) ) {
			$code    = 'rest-jobs-job-listings-retrieve-field-value-missing-id';
			$message = __( 'No job ID detected, please provide the WordPress ID of a job listing', 'matador-jobs' );
			Logger::entry( $code, $message, Severity::ERROR );
		}

		$value = null;

		switch ( $field ) {
			case 'id':
				$value = $id;
				break;
			case 'title':
				$value = get_the_title( $id );
				break;
			case 'description':
				$limit = in_array( $request['limit'], [ 'excerpt', 'full' ], true ) ? $request['limit'] : $request['limit'];
				$value = Template_Support::the_job_description( $id, $limit, 'REST_JobListings' );
				break;
			case 'applyLink':
				$value = Template_Support::the_job_apply_link( $id );
				break;
			case 'postedDate':
				$value = Template_Support::the_job_posted_date( '', $id, 'REST_JobListings' );
				break;
			case 'relativeAge':
				$value = Template_Support::the_job_relative_age( $id );
				break;
			case 'externalId':
				$value = Template_Support::the_job_external_id( $id );
				break;
			case 'link':
				$value = Template_Support::the_job_permalink( $id );
				break;
			default:
				if ( in_array( $field, Job_Taxonomies::taxonomies_list(), true ) ) {
					$value = [];
					$terms = Template_Support::the_job_terms( $field, $id );
					if ( ! empty( $terms ) ) {
						foreach ( $terms as $term ) {
							if ( ! empty( $term->name ) ) {
								$value[] = $term->name;
							}
						}
					}
				} elseif ( empty( $value ) ) {
					$value = get_post_meta( $id, $field, true );
				}
				break;
		}

		return apply_filters( 'matador_rest_job_listings_retrieve_field_value', $value, $field, $id );
	}

	/**
	 * Display Fields
	 *
	 * Returns the list of publically displayable job fields
	 *
	 * @since 3.9.0
	 *
	 * @param  WP_REST_Request $request
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public static function display_fields( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$include_options = [ 'all', 'meta', 'taxonomies', 'tax-terms' ];

		if ( ! in_array( $request['include'], $include_options, true ) ) {
			$code    = 'rest-jobs-job-listings-display-fields-unprocessable';
			$message = __( 'Invalid include argument provided, accepted values are all, meta, and tax-terms', 'matador-jobs' );
			Logger::entry( $code, $message, Severity::ERROR );
			return new WP_Error( $code, $message, [ 'status' => 422 ] );
		}

		$fields = [];

		if ( 'all' === $request['include'] || 'meta' === $request['include'] ) {

			// Retrieve meta key names from jobs, excluding fields signalled as hidden with _ prefix and non-displayable job fields.
			global $wpdb;
			$query = $wpdb->prepare(
				"
				SELECT DISTINCT pm.meta_key
				FROM {$wpdb->postmeta} pm
				INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
				WHERE p.post_type = %s
				AND pm.meta_key NOT LIKE '\_%'
				AND pm.meta_key NOT IN ( 'notes', 'assignedUsers', 'responseUser', 'owner', 'jsonld', 'category', 'shifts', 'required_skills' ) 
				",
				Matador::variable( 'post_type_key_job_listing' ),
			);

			$meta_keys = $wpdb->get_col( $query );

			if ( ! empty( $meta_keys ) ) {
				$fields['metaKeys'] = $meta_keys;
			}
		}

		if ( 'all' === $request['include'] || 'tax-terms' === $request['include'] ) {

			$taxonomies = get_object_taxonomies( Matador::variable( 'post_type_key_job_listing' ) );

			if ( is_wp_error( $taxonomies ) ) {
				$code    = 'rest-jobs-job-listings-display-fields-taxonomies-retrieval-error';
				$message = __( 'Unable to retrieve job taxonomies', 'matador-jobs' );
				Logger::entry( $code, $message, Severity::ERROR );
				return new WP_Error( $code, $message, [ 'status' => 500 ] );
			}

			if ( empty( $taxonomies ) ) {
				$code    = 'rest-jobs-job-listings-display-fields-taxonomies-no-taxonomies-retrieved';
				$message = __( 'No job taxonomies were found', 'matador-jobs' );
				Logger::entry( $code, $message, Severity::NOTICE );
			}

			$terms = get_terms(
				[
					'taxonomy'   => $taxonomies,
					'hide_empty' => false,
				]
			);

			if ( is_wp_error( $terms ) ) {
				$code    = 'rest-jobs-job-listings-display-fields-terms-retrieval-error';
				$message = __( 'Unable to retrieve job taxonomy terms', 'matador-jobs' );
				Logger::entry( $code, $message, Severity::ERROR );
				return new WP_Error( $code, $message, [ 'status' => 500 ] );
			}

			if ( empty( $terms ) ) {
				$code    = 'rest-jobs-job-listings-display-fields-no-terms-retrieved';
				$message = __( 'Unable to retrieve job taxonomy terms', 'matador-jobs' );
				Logger::entry( $code, $message, Severity::NOTICE );
			}

			$fields['taxTerms'] = $terms;
		}

		if ( 'all' === $request['include'] || 'taxonomies' === $request['include'] ) {

			$taxonomies = Job_Taxonomies::taxonomies_list();

			if ( is_wp_error( $taxonomies ) ) {
				$code    = 'rest-jobs-job-listings-display-fields-taxonomies-retrieval-error';
				$message = __( 'Unable to retrieve job taxonomies', 'matador-jobs' );
				Logger::entry( $code, $message, Severity::ERROR );
				return new WP_Error( $code, $message, [ 'status' => 500 ] );
			}

			if ( empty( $taxonomies ) ) {
				$code    = 'rest-jobs-job-listings-display-fields-no-taxonomies-retrieved';
				$message = __( 'No job taxonomies were found', 'matador-jobs' );
				Logger::entry( $code, $message, Severity::NOTICE );
			}

			$fields['registeredTaxonomies'] = $taxonomies;
		}

		return new WP_REST_Response( $fields, 200 );
	}
}
