<?php
/**
 * /llms.txt — AI-crawler-friendly summary of the dealer + inventory.
 *
 * Standalone-mode only (per ADR-19) — when AEO Optimizer is active it owns
 * the file. Adds a top-level rewrite rule so `https://example.com/llms.txt`
 * serves a Markdown document with the dealer's name + contact + a flat list
 * of every published vehicle (year, make, model, price, URL).
 *
 * Output is cached for 1 hour in a transient keyed on the inventory's last
 * modification date — flushes when any vehicle is published or updated.
 *
 * @package ThemeREX\AutoDealer
 */

declare(strict_types=1);

namespace ThemeREX\AutoDealer\Frontend;

defined( 'ABSPATH' ) || exit;
use ThemeREX\AutoDealer\Integrations\Schema\SchemaGenerator;
use ThemeREX\AutoDealer\PostTypes\DealerPostType;
use ThemeREX\AutoDealer\PostTypes\VehiclePostType;
use function ThemeREX\AutoDealer\formatPrice;
use function ThemeREX\AutoDealer\getVehicleSpecs;

class LlmsTxt {

	public const QUERY_VAR = 'trx_ad_llms_txt';
	public const TRANSIENT = 'trx_ad_llms_txt';
	public const CACHE_TTL = HOUR_IN_SECONDS;

	public function register(): void {
		add_action( 'init', array( $this, 'addRewrite' ) );
		add_filter( 'query_vars', array( $this, 'addQueryVar' ) );
		add_filter( 'redirect_canonical', array( $this, 'skipCanonical' ), 10, 2 );
		// `parse_request` runs *before* canonical redirect + 404 handling
		// and works without pretty permalinks (rewrite rules don't fire on
		// default permalink structure).
		add_action( 'parse_request', array( $this, 'detectAndServe' ), 1 );
		add_action( 'template_redirect', array( $this, 'maybeServe' ) );
		add_action( 'save_post_' . VehiclePostType::POST_TYPE, array( self::class, 'flushCache' ) );
		add_action( 'save_post_' . DealerPostType::POST_TYPE, array( self::class, 'flushCache' ) );
	}

	/**
	 * Direct URL match — fires whether or not pretty permalinks are active.
	 * Catches `https://example.com/llms.txt` regardless of WP's rewrite
	 * configuration.
	 */
	public function detectAndServe( \WP $wp ): void {
		$path = isset( $_SERVER['REQUEST_URI'] )
			? (string) wp_parse_url( esc_url_raw( wp_unslash( (string) $_SERVER['REQUEST_URI'] ) ), PHP_URL_PATH )
			: '';
		if ( '' === $path ) {
			return;
		}
		$basename = strtolower( ltrim( $path, '/' ) );
		if ( 'llms.txt' === $basename || 'llms.txt/' === $basename ) {
			$wp->query_vars[ self::QUERY_VAR ] = '1';
			$this->serve();
		}
	}

	/**
	 * Suppress WP's automatic trailing-slash redirect for /llms.txt.
	 * Without this, `/llms.txt` 301s to `/llms.txt/` and the query var
	 * never gets set on the original request.
	 *
	 * @param mixed $redirect WP's would-be redirect URL (string|false).
	 * @param mixed $request  Raw URL path being canonicalized.
	 * @return string|false False to suppress redirect; otherwise pass through.
	 */
	public function skipCanonical( $redirect, $request ) {
		if ( get_query_var( self::QUERY_VAR ) ) {
			return false;
		}
		if ( is_string( $request ) && str_contains( $request, 'llms.txt' ) ) {
			return false;
		}
		return $redirect;
	}

	public function addRewrite(): void {
		add_rewrite_rule( '^llms\.txt$', 'index.php?' . self::QUERY_VAR . '=1', 'top' );
	}

	/**
	 * @param array<int, string> $vars
	 * @return array<int, string>
	 */
	public function addQueryVar( array $vars ): array {
		$vars[] = self::QUERY_VAR;
		return $vars;
	}

	public function maybeServe(): void {
		if ( ! get_query_var( self::QUERY_VAR ) ) {
			return;
		}
		$this->serve();
	}

	private function serve(): void {
		$mode       = SchemaGenerator::detectMode();
		$is_aeo     = SchemaGenerator::MODE_AEO === $mode;
		$is_default = SchemaGenerator::MODE_STANDALONE === $mode;

		/**
		 * Filter whether `/llms.txt` should be served on this request. Free
		 * tier serves only on Standalone mode and lets AEO Optimizer own
		 * the file when AEO is active. Pro AEO (Phase 18) hooks this filter
		 * to widen serving across Yoast/Rank Math/AIOSEO/SEOPress/TSF too,
		 * but still leaves AEO mode alone (AEO emits its own).
		 *
		 * @param bool   $should_serve Default: standalone-only.
		 * @param string $mode         Active SchemaGenerator mode.
		 */
		$should_serve = (bool) apply_filters( 'trx_ad_llms_txt_should_serve', $is_default, $mode );
		if ( $is_aeo || ! $should_serve ) {
			return;
		}

		nocache_headers();
		header( 'Content-Type: text/plain; charset=utf-8' );

		$cached = get_transient( self::TRANSIENT );
		if ( false === $cached ) {
			$cached = $this->build();
			set_transient( self::TRANSIENT, $cached, self::CACHE_TTL );
		}

		// Plain-text (text/plain) response body built entirely from esc-free
		// Markdown in build(); HTML escaping functions would corrupt the
		// non-HTML output (e.g. `&` -> `&amp;` inside URLs).
		echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- text/plain stream, not an HTML context.
		exit;
	}

	public static function flushCache( int $postId = 0 ): void {
		// Skip auto-saves and revisions.
		if ( $postId > 0 && ( wp_is_post_revision( $postId ) || wp_is_post_autosave( $postId ) ) ) {
			return;
		}
		delete_transient( self::TRANSIENT );
	}

	private function build(): string {
		$lines   = array();
		$lines[] = '# ' . get_bloginfo( 'name' );
		$lines[] = '';
		$lines[] = '> ' . get_bloginfo( 'description' );
		$lines[] = '';
		$lines[] = 'Site: ' . home_url( '/' );
		$lines[] = '';

		// Dealers section.
		$dealers = get_posts(
			array(
				'post_type'      => DealerPostType::POST_TYPE,
				'posts_per_page' => 50,
				'post_status'    => 'publish',
			)
		);
		if ( ! empty( $dealers ) ) {
			$lines[] = '## Dealers';
			$lines[] = '';
			foreach ( $dealers as $dealer ) {
				$lines[] = '- [' . $dealer->post_title . '](' . get_permalink( $dealer ) . ')';
			}
			$lines[] = '';
		}

		// Inventory section.
		$vehicles = get_posts(
			array(
				'post_type'      => VehiclePostType::POST_TYPE,
				'posts_per_page' => 500,
				'post_status'    => 'publish',
				'orderby'        => 'date',
				'order'          => 'DESC',
			)
		);

		$lines[] = '## Inventory';
		$lines[] = '';
		if ( empty( $vehicles ) ) {
			$lines[] = '_No vehicles currently listed._';
		} else {
			foreach ( $vehicles as $v ) {
				$specs = getVehicleSpecs( $v->ID );
				$bits  = array();
				if ( ! empty( $specs['year'] ) ) {
					$bits[] = (int) $specs['year'];
				}
				if ( ! empty( $specs['mileage'] ) ) {
					$bits[] = number_format( (float) $specs['mileage'], 0 ) . ' km';
				}
				if ( ! empty( $specs['price'] ) ) {
					$bits[] = formatPrice( (float) $specs['price'], false );
				} elseif ( ! empty( $specs['price_on_request'] ) ) {
					$bits[] = __( 'Price on request', 'trx-auto-dealer' );
				}

				$line = '- [' . $v->post_title . '](' . get_permalink( $v ) . ')';
				if ( ! empty( $bits ) ) {
					$line .= ' — ' . implode( ' · ', $bits );
				}
				$lines[] = $line;
			}
		}

		$lines[] = '';
		$lines[] = '_Generated by ThemeREX Auto Dealer · Last update: ' . gmdate( 'c' ) . '_';
		$lines[] = '';

		/**
		 * Filter the rendered llms.txt body.
		 *
		 * @param string $body
		 */
		return (string) apply_filters( 'trx_ad_llms_txt', implode( "\n", $lines ) );
	}
}
