<?php
/**
 * Durable orchestration for the final WordPress publication step.
 *
 * Uses the existing publication ledger as the single source of truth. A CLI
 * worker may consume the queue, but a bounded loopback/cron fallback is always
 * available so regular WordPress users need no server configuration.
 *
 * @package Contentosapp_Studio
 * @since   1.6.0
 */

defined( 'ABSPATH' ) || exit;

class Costudio_Publication_Jobs {

	const CRON_HOOK = 'costudio_publication_job_run';
	const REST_PATH = '/run-publication';
	const MODE_AUTO = 'auto';
	const MODE_LEGACY = 'legacy';
	const WEB_LEASE_SECONDS = 360;
	const CLI_LEASE_SECONDS = 900;

	/** Register the self-healing dispatch hooks. */
	public static function register_hooks(): void {
		add_action( self::CRON_HOOK, array( __CLASS__, 'cron_run' ), 10, 1 );
	}

	/** Whether new final-publication steps use the durable ledger queue. */
	public static function enabled(): bool {
		if ( defined( 'COSTUDIO_DURABLE_PUBLICATION' ) ) {
			return (bool) COSTUDIO_DURABLE_PUBLICATION;
		}
		$mode = sanitize_key( (string) get_option( 'costudio_publication_execution_mode', self::MODE_AUTO ) );
		return self::MODE_LEGACY !== $mode;
	}

	/**
	 * Create or revive the single publication job for a production.
	 *
	 * @return array<string,mixed>|null
	 */
	public static function ensure_queued( int $production_id, string $source = 'pipeline' ): ?array {
		global $wpdb;
		if ( $production_id <= 0 || ! Costudio_Publication_Ledger::is_ready() ) {
			return null;
		}
		$production = Costudio_Production::get_by_id( $production_id );
		if ( ! $production ) {
			return null;
		}

		$table = $wpdb->prefix . Costudio_Publication_Ledger::TABLE;
		$row   = self::find( $production_id );
		$now   = current_time( 'mysql', true );
		if ( $row ) {
			if ( in_array( (string) ( $row['job_status'] ?? '' ), array( 'completed', 'cancelled' ), true ) || 'succeeded' === (string) $row['state'] ) {
				if ( 'cancelled' !== (string) ( $row['job_status'] ?? '' ) ) {
					self::reconcile_completed_production( $production, $row );
				}
				return $row;
			}
			if ( in_array( (string) ( $row['job_status'] ?? '' ), array( 'queued', 'processing' ), true ) ) {
				return $row;
			}
			$job_id = (string) ( $row['job_id'] ?? '' );
			if ( '' === $job_id ) {
				$job_id = wp_generate_uuid4();
			}
			// A user-triggered publication retry may revive a terminal failed pass.
			$data = array(
				'job_id'       => $job_id,
				'job_status'   => 'queued',
				'stage'        => 'queued',
				'state'        => ! empty( $row['post_id'] ) ? 'created' : 'queued',
				'available_at' => $now,
				'worker_id'    => null,
				'lease_expires_at' => null,
				'heartbeat_at' => $now,
				'claim_token'  => null,
				'claimed_at'   => null,
				'error_code'   => null,
				'error_message'=> null,
				'terminal_source' => null,
				'finished_at'  => null,
				'updated_at'   => $now,
			);
			$wpdb->update( $table, $data, array( 'id' => (int) $row['id'] ) );
			self::clear_cache( $production_id );
			$row = self::find( $production_id );
		} else {
			$uid = Costudio_Production::tracking_uid_for( $production_id );
			$site_key = (string) ( $production->site_key ?? '' );
			$idempotency_key = Costudio_Publication_Ledger::idempotency_key( $site_key, $uid );
			$seed = wp_json_encode( array( 'production_id' => $production_id, 'source' => sanitize_key( $source ) ) );
			$wpdb->insert(
				$table,
				array(
					'job_id'         => wp_generate_uuid4(),
					'idempotency_key' => $idempotency_key,
					'production_id'  => $production_id,
					'production_uid' => $uid,
					'site_key'       => $site_key,
					'origin_hash'    => hash( 'sha256', Costudio_Publication_Ledger::origin_fingerprint() ),
					'payload_hash'   => hash( 'sha256', (string) $seed ),
					'payload_json'   => $seed,
					'state'          => 'queued',
					'job_status'     => 'queued',
					'stage'          => 'queued',
					'attempts'       => 0,
					'available_at'   => $now,
					'heartbeat_at'   => $now,
					'created_at'     => $now,
					'updated_at'     => $now,
				),
				array( '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s' )
			);
			self::clear_cache( $production_id );
			$row = self::find( $production_id );
		}

		if ( $row ) {
			self::log_event( 'info', 'publication_jobs.queued', $row, array( 'source' => sanitize_key( $source ) ) );
		}
		return $row;
	}

	/** Finish the local production if PHP died after the post ledger committed. */
	private static function reconcile_completed_production( $production, array $row ): bool {
		$production_id = (int) ( $production->id ?? $row['production_id'] ?? 0 );
		$post_id       = (int) ( $row['post_id'] ?? 0 );
		if ( $production_id <= 0 || $post_id <= 0 || 'completed' === (string) ( $production->status ?? '' ) ) {
			return false;
		}
		Costudio_Production::set_completed(
			$production_id,
			$post_id,
			(string) ( $row['post_url'] ?? '' ),
			(string) ( $row['edit_url'] ?? '' ),
			(string) ( $production->title ?? '' )
		);
		self::log_event( 'warn', 'publication_jobs.completion_reconciled', $row, array(
			'previous_production_status' => sanitize_key( (string) ( $production->status ?? '' ) ),
			'post_id' => $post_id,
		) );
		Costudio_Runner::pipeline_log_append(
			$production_id,
			__( 'A publicação já havia sido criada e foi reconciliada automaticamente após a interrupção do executor.', 'contentosapp-studio' ),
			'warn'
		);
		return true;
	}

	/** Find by production, bypassing the older short ledger cache. */
	public static function find( int $production_id ): ?array {
		global $wpdb;
		$row = $wpdb->get_row(
			$wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . Costudio_Publication_Ledger::TABLE . ' WHERE production_id=%d LIMIT 1', $production_id ),
			ARRAY_A
		);
		return is_array( $row ) ? $row : null;
	}

	/** Atomically claim one queued publication job. */
	public static function claim_next( string $worker_id, string $driver, int $lease_seconds, string $specific_job_id = '' ): ?array {
		global $wpdb;
		self::recover_expired( 20 );
		$table = $wpdb->prefix . Costudio_Publication_Ledger::TABLE;
		$now   = current_time( 'mysql', true );
		$where = "job_status='queued' AND available_at <= %s";
		$args  = array( $now );
		if ( '' !== $specific_job_id ) {
			$where .= ' AND job_id=%s';
			$args[] = $specific_job_id;
		}
		$id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table} WHERE {$where} ORDER BY available_at ASC,id ASC LIMIT 1", ...$args ) );
		if ( ! $id ) {
			return null;
		}
		$lease = gmdate( 'Y-m-d H:i:s', time() + max( 60, $lease_seconds ) );
		$updated = $wpdb->query(
			$wpdb->prepare(
				"UPDATE {$table} SET job_status='processing',stage='preparing_media',driver=%s,worker_id=%s,claim_count=claim_count+1,heartbeat_at=%s,lease_expires_at=%s,stage_started_at=%s,updated_at=%s WHERE id=%d AND job_status='queued'",
				$driver, $worker_id, $now, $lease, $now, $now, (int) $id
			)
		);
		if ( 1 !== (int) $updated ) {
			return null;
		}
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id=%d", (int) $id ), ARRAY_A );
		if ( is_array( $row ) ) {
			self::clear_cache( (int) $row['production_id'] );
			self::log_event( 'info', 'publication_jobs.claimed', $row );
			Costudio_Runner::pipeline_log_append( (int) $row['production_id'], __( 'Executor de publicação iniciado; preparando o rascunho no WordPress.', 'contentosapp-studio' ), 'info' );
			return $row;
		}
		return null;
	}

	/** Persist an execution-stage heartbeat. */
	public static function stage( int $production_id, string $stage, string $message = '' ): void {
		global $wpdb;
		$row = self::find( $production_id );
		if ( ! $row || 'processing' !== (string) ( $row['job_status'] ?? '' ) ) {
			return;
		}
		$stage = sanitize_key( $stage );
		$now = current_time( 'mysql', true );
		$data = array( 'heartbeat_at' => $now, 'updated_at' => $now );
		if ( $stage !== (string) ( $row['stage'] ?? '' ) ) {
			$data['stage'] = $stage;
			$data['stage_started_at'] = $now;
		}
		$wpdb->update( $wpdb->prefix . Costudio_Publication_Ledger::TABLE, $data, array( 'id' => (int) $row['id'] ) );
		self::clear_cache( $production_id );
		if ( $stage !== (string) ( $row['stage'] ?? '' ) ) {
			self::log_event( 'info', 'publication_jobs.stage_changed', array_merge( $row, array( 'stage' => $stage ) ), array( 'message' => $message ) );
		}
	}

	public static function heartbeat( int $production_id, int $lease_seconds ): void {
		global $wpdb;
		$now = current_time( 'mysql', true );
		$wpdb->query( $wpdb->prepare(
			'UPDATE ' . $wpdb->prefix . Costudio_Publication_Ledger::TABLE . ' SET heartbeat_at=%s,lease_expires_at=%s,updated_at=%s WHERE production_id=%d AND job_status=\'processing\'',
			$now, gmdate( 'Y-m-d H:i:s', time() + max( 60, $lease_seconds ) ), $now, $production_id
		) );
	}

	public static function requeue( int $production_id, string $reason, int $delay = 1 ): bool {
		global $wpdb;
		$now = current_time( 'mysql', true );
		$ok = $wpdb->update(
			$wpdb->prefix . Costudio_Publication_Ledger::TABLE,
			array(
				'job_status' => 'queued', 'available_at' => gmdate( 'Y-m-d H:i:s', time() + max( 0, $delay ) ),
				'worker_id' => null, 'lease_expires_at' => null, 'heartbeat_at' => $now, 'updated_at' => $now,
			),
			array( 'production_id' => $production_id )
		);
		self::clear_cache( $production_id );
		$row = self::find( $production_id );
		if ( false !== $ok && $row ) {
			self::log_event( 'info', 'publication_jobs.requeued', $row, array( 'reason' => sanitize_key( $reason ) ) );
		}
		return false !== $ok;
	}

	public static function complete( int $production_id, string $source = 'worker' ): bool {
		$done = self::terminal( $production_id, 'completed', 'completed', '', $source );
		if ( ! $done ) {
			$row = self::find( $production_id );
			if ( $row && ( 'completed' === (string) ( $row['job_status'] ?? '' ) || 'succeeded' === (string) ( $row['state'] ?? '' ) ) ) {
				self::log_event( 'info', 'publication_jobs.completed', $row, array( 'terminal_source' => sanitize_key( $source ) ) );
				return true;
			}
		}
		return $done;
	}

	public static function fail( int $production_id, string $code, string $error, string $source = 'worker' ): bool {
		$row = self::find( $production_id );
		$done = self::terminal( $production_id, 'failed', 'failed', $code, $source, $error, array(
			'failed_stage' => (string) ( $row['stage'] ?? '' ),
		) );
		if ( ! $done ) {
			$current = self::find( $production_id );
			if ( $current && 'failed' === (string) ( $current['job_status'] ?? '' ) ) {
				self::log_event( 'error', 'publication_jobs.failed', $current, array(
					'error_code' => '' !== (string) ( $current['error_code'] ?? '' ) ? (string) $current['error_code'] : $code,
					'failed_stage' => (string) ( $row['stage'] ?? '' ),
					'terminal_already_set' => true,
				) );
			}
		}
		return $done;
	}

	private static function terminal( int $production_id, string $status, string $stage, string $code, string $source, string $error = '', array $log_extra = array() ): bool {
		global $wpdb;
		$before = self::find( $production_id );
		$now = current_time( 'mysql', true );
		$updated = $wpdb->query( $wpdb->prepare(
			'UPDATE ' . $wpdb->prefix . Costudio_Publication_Ledger::TABLE . ' SET job_status=%s,stage=%s,error_code=%s,error_message=%s,terminal_source=%s,finished_at=%s,worker_id=NULL,lease_expires_at=NULL,claim_token=NULL,claimed_at=NULL,heartbeat_at=%s,updated_at=%s WHERE production_id=%d AND job_status IN (\'processing\',\'queued\')',
			$status, $stage, $code, mb_substr( $error, 0, 2000 ), sanitize_key( $source ), $now, $now, $now, $production_id
		) );
		self::clear_cache( $production_id );
		$row = self::find( $production_id );
		if ( 1 === (int) $updated && $row ) {
			$job_id = (string) ( $before['job_id'] ?? $row['job_id'] ?? '' );
			if ( '' !== $job_id ) {
				wp_clear_scheduled_hook( self::CRON_HOOK, array( $job_id ) );
			}
			self::log_event( 'failed' === $status ? 'error' : 'info', 'publication_jobs.' . $status, $row, array_merge( array( 'error_code' => $code ), $log_extra ) );
		}
		return 1 === (int) $updated;
	}

	/** Recover jobs whose owner disappeared. Idempotent post checkpoints make retry safe. */
	public static function recover_expired( int $limit = 20 ): int {
		global $wpdb;
		$table = $wpdb->prefix . Costudio_Publication_Ledger::TABLE;
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT * FROM {$table} WHERE job_status='processing' AND lease_expires_at IS NOT NULL AND lease_expires_at < %s ORDER BY id ASC LIMIT %d",
			current_time( 'mysql', true ), max( 1, $limit )
		), ARRAY_A );
		$count = 0;
		foreach ( $rows as $row ) {
			$updated = $wpdb->query( $wpdb->prepare(
				"UPDATE {$table} SET job_status='queued',available_at=%s,worker_id=NULL,lease_expires_at=NULL,claim_token=NULL,claimed_at=NULL,updated_at=%s WHERE id=%d AND job_status='processing' AND lease_expires_at < %s",
				current_time( 'mysql', true ), current_time( 'mysql', true ), (int) $row['id'], current_time( 'mysql', true )
			) );
			if ( 1 === (int) $updated ) {
				$count++;
				self::clear_cache( (int) $row['production_id'] );
				self::log_event( 'warn', 'publication_jobs.lease_recovered', $row, array( 'previous_worker_id' => (string) ( $row['worker_id'] ?? '' ) ) );
				Costudio_Runner::pipeline_log_append( (int) $row['production_id'], __( 'O executor anterior de publicação foi interrompido; a publicação foi recuperada automaticamente sem regenerar os agentes.', 'contentosapp-studio' ), 'warn' );
			}
		}
		return $count;
	}

	/** Ensure a live job has a worker without requiring any site-owner setup. */
	public static function ensure_progress( int $production_id, string $source = 'poll' ): bool {
		self::recover_expired( 20 );
		$row = self::find( $production_id );
		if ( ! $row || ! in_array( (string) ( $row['job_status'] ?? '' ), array( 'queued', 'processing' ), true ) ) {
			return false;
		}
		if ( 'processing' === (string) $row['job_status'] ) {
			return true;
		}
		// A healthy optional CLI worker will claim within seconds. The fallback is
		// still scheduled immediately, but its atomic claim simply loses that race.
		self::kick_web_fallback( $row, $source );
		return true;
	}

	public static function has_live_job( int $production_id ): bool {
		$row = self::find( $production_id );
		return is_array( $row ) && in_array( (string) ( $row['job_status'] ?? '' ), array( 'queued', 'processing' ), true );
	}

	public static function cancel_for_production( int $production_id, string $source = 'user' ): bool {
		global $wpdb;
		$row = self::find( $production_id );
		$now = current_time( 'mysql', true );
		$updated = $wpdb->query( $wpdb->prepare(
			'UPDATE ' . $wpdb->prefix . Costudio_Publication_Ledger::TABLE . " SET job_status='cancelled',stage='cancelled',terminal_source=%s,finished_at=%s,worker_id=NULL,lease_expires_at=NULL,claim_token=NULL,claimed_at=NULL,updated_at=%s WHERE production_id=%d AND job_status IN ('queued','processing')",
			sanitize_key( $source ), $now, $now, $production_id
		) );
		self::clear_cache( $production_id );
		if ( 1 === (int) $updated && ! empty( $row['job_id'] ) ) {
			wp_clear_scheduled_hook( self::CRON_HOOK, array( (string) $row['job_id'] ) );
		}
		return 1 === (int) $updated;
	}

	/** Recover queued jobs even when no production detail page is open. */
	public static function sweep( int $limit = 20 ): int {
		global $wpdb;
		self::recover_expired( $limit );
		$ids = $wpdb->get_col( $wpdb->prepare(
			"SELECT production_id FROM {$wpdb->prefix}" . Costudio_Publication_Ledger::TABLE . " WHERE job_status='queued' AND available_at<=%s ORDER BY id ASC LIMIT %d",
			current_time( 'mysql', true ), max( 1, $limit )
		) );
		foreach ( $ids as $id ) {
			self::ensure_progress( (int) $id, 'scheduled_sweep' );
		}
		return count( $ids );
	}

	/** Non-blocking local fallback plus WP-Cron safety net. */
	public static function kick_web_fallback( array $row, string $source = 'queue' ): void {
		$job_id = sanitize_text_field( (string) ( $row['job_id'] ?? '' ) );
		if ( '' === $job_id ) {
			return;
		}
		$cli_healthy = self::cli_worker_healthy();
		if ( ! wp_next_scheduled( self::CRON_HOOK, array( $job_id ) ) ) {
			$scheduled = wp_schedule_single_event( time() + ( $cli_healthy ? 30 : 5 ), self::CRON_HOOK, array( $job_id ), true );
			if ( is_wp_error( $scheduled ) ) {
				self::log_event( 'warn', 'publication_jobs.cron_schedule_failed', $row, array(
					'source' => sanitize_key( $source ),
					'error_code' => sanitize_key( $scheduled->get_error_code() ),
				) );
			} elseif ( ! $cli_healthy && ( ! defined( 'DISABLE_WP_CRON' ) || ! DISABLE_WP_CRON ) && function_exists( 'spawn_cron' ) ) {
				// Standard WordPress installations need no external cron configuration.
				spawn_cron( time() );
			}
		}
		if ( $cli_healthy ) {
			return;
		}
		$debounce = 'costudio_pub_kick_' . md5( $job_id );
		if ( get_transient( $debounce ) ) {
			return;
		}
		set_transient( $debounce, 1, 5 );
		$url = rest_url( 'costudio/v1' . self::REST_PATH );
		$response = wp_remote_post( $url, array(
			'timeout' => 0.01,
			'blocking' => false,
			'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
			'body' => array( 'job_id' => $job_id, 'token' => self::rest_token( $job_id ) ),
		) );
		self::log_event( is_wp_error( $response ) ? 'warn' : 'info', 'publication_jobs.web_fallback_kicked', $row, array(
			'source' => sanitize_key( $source ), 'loopback_error' => is_wp_error( $response ) ? $response->get_error_code() : '',
		) );
	}

	public static function register_rest_route(): void {
		register_rest_route( 'costudio/v1', self::REST_PATH, array(
			'methods' => 'POST', 'callback' => array( __CLASS__, 'rest_run' ),
			'permission_callback' => array( __CLASS__, 'rest_permission' ),
		) );
	}

	public static function rest_permission( WP_REST_Request $request ) {
		$job_id = sanitize_text_field( (string) $request->get_param( 'job_id' ) );
		$token  = sanitize_text_field( (string) $request->get_param( 'token' ) );
		if ( '' === $job_id || '' === $token || ! hash_equals( self::rest_token( $job_id ), $token ) ) {
			return new WP_Error( 'costudio_publication_invalid_token', __( 'Invalid publication worker token.', 'contentosapp-studio' ), array( 'status' => 403 ) );
		}
		return true;
	}

	public static function rest_run( WP_REST_Request $request ): WP_REST_Response {
		$job_id = sanitize_text_field( (string) $request->get_param( 'job_id' ) );
		$result = Costudio_Publication_Worker::run_one( $job_id, 'web_fallback' );
		return new WP_REST_Response( array( 'ok' => true, 'result' => $result ), 200 );
	}

	public static function cron_run( string $job_id ): void {
		Costudio_Publication_Worker::run_one( sanitize_text_field( $job_id ), 'wp_cron' );
	}

	public static function queue_depth(): int {
		global $wpdb;
		return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}" . Costudio_Publication_Ledger::TABLE . " WHERE job_status='queued'" );
	}

	/** Safe diagnostics for the technical panel and CLI. */
	public static function diagnostics( int $production_id ): array {
		$row = self::find( $production_id );
		if ( ! $row ) {
			return array();
		}
		return array(
			'job_id' => (string) ( $row['job_id'] ?? '' ), 'job_status' => (string) ( $row['job_status'] ?? '' ),
			'stage' => (string) ( $row['stage'] ?? '' ), 'driver' => (string) ( $row['driver'] ?? '' ),
			'claim_count' => (int) ( $row['claim_count'] ?? 0 ), 'worker_id' => (string) ( $row['worker_id'] ?? '' ),
			'heartbeat_at' => (string) ( $row['heartbeat_at'] ?? '' ), 'lease_expires_at' => (string) ( $row['lease_expires_at'] ?? '' ),
			'last_error_code' => (string) ( $row['error_code'] ?? '' ), 'updated_at' => (string) ( $row['updated_at'] ?? '' ),
		);
	}

	public static function list_rows( int $limit = 20 ): array {
		global $wpdb;
		$limit = max( 1, min( 200, $limit ) );
		return (array) $wpdb->get_results( $wpdb->prepare(
			'SELECT job_id,job_status,stage,production_id,driver,claim_count,worker_id,heartbeat_at,created_at,updated_at,finished_at FROM ' . $wpdb->prefix . Costudio_Publication_Ledger::TABLE . ' ORDER BY id DESC LIMIT %d', $limit
		), ARRAY_A );
	}

	/** Remove resumable content after seven days and audit rows after ninety. */
	public static function cleanup(): array {
		global $wpdb;
		$table = $wpdb->prefix . Costudio_Publication_Ledger::TABLE;
		$purged = $wpdb->query(
			"UPDATE {$table} SET payload_json=NULL WHERE finished_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL 7 DAY) AND job_status IN ('completed','failed','cancelled') AND payload_json IS NOT NULL"
		);
		$deleted = $wpdb->query(
			"DELETE FROM {$table} WHERE finished_at < DATE_SUB(UTC_TIMESTAMP(), INTERVAL 90 DAY) AND job_status IN ('completed','failed','cancelled')"
		);
		$result = array( 'payloads_purged' => max( 0, (int) $purged ), 'rows_deleted' => max( 0, (int) $deleted ) );
		if ( $result['payloads_purged'] || $result['rows_deleted'] ) {
			self::log_event( 'info', 'publication_jobs.cleanup', array(), $result );
		}
		return $result;
	}

	private static function rest_token( string $job_id ): string {
		return hash_hmac( 'sha256', $job_id, wp_salt( 'auth' ) );
	}

	private static function cli_worker_healthy(): bool {
		$heartbeat = get_option( 'costudio_byok_worker_heartbeat', array() );
		return is_array( $heartbeat )
			&& 'cli' === (string) ( $heartbeat['sapi'] ?? '' )
			&& (int) ( $heartbeat['timestamp'] ?? 0 ) >= ( time() - 60 )
			&& 'stopped' !== (string) ( $heartbeat['state'] ?? '' );
	}

	private static function clear_cache( int $production_id ): void {
		wp_cache_delete( 'costudio_pub_ledger_' . $production_id, 'costudio' );
	}

	public static function log_event( string $level, string $event, array $row, array $extra = array() ): void {
		$production_id = (int) ( $row['production_id'] ?? 0 );
		$context = array_merge( array(
			'job_id' => (string) ( $row['job_id'] ?? '' ), 'production_id' => $production_id,
			'status' => (string) ( $row['job_status'] ?? '' ), 'stage' => (string) ( $row['stage'] ?? '' ),
			'driver' => (string) ( $row['driver'] ?? '' ), 'claim_count' => (int) ( $row['claim_count'] ?? 0 ),
			'php_sapi' => PHP_SAPI, 'pid' => function_exists( 'getmypid' ) ? (int) getmypid() : 0,
		), $extra );
		if ( 'error' === $level ) {
			Costudio_Logger::error( $event, $context, $production_id );
		} elseif ( 'warn' === $level ) {
			Costudio_Logger::warn( $event, $context, $production_id );
		} else {
			Costudio_Logger::info( $event, $context, $production_id );
		}
		if ( function_exists( 'costudio_log' ) ) {
			costudio_log( $event, $context, $level, 'info' === $level );
		}
	}
}
