<?php
/**
 * File: modules/heading-audit-endpoint.php
 *
 * REST:
 *   GET /wp-json/myaio/v1/heading-audit?url=https://example.com/page[&limit_words=60]
 *
 * Auth:
 * - Header: x-api-key (recommended)
 * - Query/body: key (legacy)
 * - Query/body: api_key (alternate)
 *
 * Summary:
 *   - Parses H1..H6 from the given URL (excluding header/footer/nav/navigation role)
 *   - Returns counts and per-heading details (text, chars, words, id, class)
 *   - Issues: missing H1..H6, multiple H1, empty headings, duplicate text per level,
 *             and any heading with words > limit_words (default 60)
 *   - Adds boolean flags (h1/h2/h3) after total_headings in summary
 *   - Adds "order" list with headings in top-to-bottom DOM order across all levels
 */

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

/**
 * Ensure your shared utils are loaded before this module:
 * require_once plugin_dir_path( __FILE__ ) . '../includes/sac-utils.php';
 *
 * This module now uses:
 * - myaio_sac_rest_permission_check()
 * - myaio_sac_get_incoming_api_key() / myaio_sac_validate_api_key() via permission callback
 */

add_action(
	'rest_api_init',
	function () {
		register_rest_route(
			'myaio/v1',
			'/heading-audit',
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => 'myaio_sac_heading_audit',
				'permission_callback' => 'myaio_sac_rest_permission_check',
				'args'                => [
					'url'         => [
						'required'          => true,
						'type'              => 'string',
						'sanitize_callback' => 'esc_url_raw',
					],
					'limit_words' => [
						'required'          => false,
						'type'              => 'integer',
						'sanitize_callback' => 'absint',
					],
					// kept for backward compatibility; actual auth handled by util
					'key'         => [
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					],
					'api_key'      => [
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					],
				],
			]
		);
	}
);

/**
 * Main endpoint callback
 */
function myaio_sac_heading_audit( WP_REST_Request $request ) {
	$url = trim( (string) $request->get_param( 'url' ) );

	if ( '' === $url || ! wp_http_validate_url( $url ) ) {
		return new WP_REST_Response(
			[ 'error' => 'Provide a valid ?url' ],
			400
		);
	}

	// Optional override: words limit (default 60)
	$limit_words = (int) $request->get_param( 'limit_words' );
	if ( $limit_words < 1 ) {
		$limit_words = 60;
	}

	$res = myaio_sac_heading_fetch_body( $url );

	if ( empty( $res['ok'] ) || empty( $res['body'] ) ) {
		// Keep behavior similar (error + details) but do not expose raw internals beyond what you already do.
		return new WP_REST_Response(
			[
				'error'   => 'Failed to fetch body',
				'details' => $res,
			],
			502
		);
	}

	$html = (string) $res['body'];

	// Be forgiving with encodings
	if ( function_exists( 'mb_convert_encoding' ) ) {
		$html = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' );
	}

	if ( ! class_exists( 'DOMDocument' ) ) {
		return new WP_REST_Response(
			[ 'error' => 'DOM extension is not available on this server.' ],
			500
		);
	}

	libxml_use_internal_errors( true );
	$dom = new DOMDocument();
	// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
	@$dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
	libxml_clear_errors();

	$xpath = new DOMXPath( $dom );

	$levels  = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
	$details = [];
	$counts  = [];

	foreach ( $levels as $level ) {
		// Exclude header/footer/nav + role="navigation"
		$nodes = $xpath->query(
			'//' . $level .
			'[not(ancestor::header) and not(ancestor::footer) and not(ancestor::nav) and not(ancestor::*[@role="navigation"])]'
		);

		$list = [];
		$i    = 0;

		if ( $nodes && $nodes->length ) {
			foreach ( $nodes as $node ) {
				$text = trim( preg_replace( '/\s+/u', ' ', (string) $node->textContent ) );

				$chars = function_exists( 'mb_strlen' )
					? mb_strlen( $text, 'UTF-8' )
					: strlen( $text );

				$words = ( '' !== $text )
					? count( preg_split( '/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY ) )
					: 0;

				$list[] = [
					'index' => $i++,
					'text'  => $text,
					'chars' => $chars,
					'words' => $words,
					'id'    => $node->hasAttribute( 'id' ) ? (string) $node->getAttribute( 'id' ) : null,
					'class' => $node->hasAttribute( 'class' ) ? (string) $node->getAttribute( 'class' ) : null,
				];
			}
		}

		$key            = strtoupper( $level );
		$details[ $key ] = $list;
		$counts[ $key ]  = count( $list );
	}

	/* =========================================================================
	 * DOM order (top-to-bottom) across ALL headings
	 * ========================================================================= */
	$flat = [];

	$all = $xpath->query(
		'(//h1|//h2|//h3|//h4|//h5|//h6)'
		. '[not(ancestor::header) and not(ancestor::footer) and not(ancestor::nav) and not(ancestor::*[@role="navigation"])]'
	);

	$pos = 0;

	if ( $all && $all->length ) {
		foreach ( $all as $node ) {
			$tag  = strtoupper( (string) $node->nodeName ); // H1..H6
			$text = trim( preg_replace( '/\s+/u', ' ', (string) $node->textContent ) );

			$chars = function_exists( 'mb_strlen' )
				? mb_strlen( $text, 'UTF-8' )
				: strlen( $text );

			$words = ( '' !== $text )
				? count( preg_split( '/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY ) )
				: 0;

			$flat[] = [
				'pos'   => $pos++,
				'level' => $tag,
				'text'  => $text,
				'chars' => $chars,
				'words' => $words,
				'id'    => $node->hasAttribute( 'id' ) ? (string) $node->getAttribute( 'id' ) : null,
				'class' => $node->hasAttribute( 'class' ) ? (string) $node->getAttribute( 'class' ) : null,
			];
		}
	}

	// Build issues summary
	$issues = [];

	// Missing levels
	foreach ( [ 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ] as $k ) {
		if ( ( $counts[ $k ] ?? 0 ) === 0 ) {
			$issues[] = $k . ' is missing.';
		}
	}

	// Multiple H1
	if ( ( $counts['H1'] ?? 0 ) > 1 ) {
		$issues[] = 'Multiple H1 tags found.';
	}

	// Over-long by WORDS + empty
	foreach ( [ 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ] as $k ) {
		foreach ( (array) ( $details[ $k ] ?? [] ) as $h ) {
			if ( (int) ( $h['words'] ?? 0 ) > $limit_words ) {
				$issues[] = sprintf(
					'%s[%d] exceeds %d words.',
					$k,
					(int) ( $h['index'] ?? 0 ),
					(int) $limit_words
				);
			}
			if ( '' === (string) ( $h['text'] ?? '' ) ) {
				$issues[] = sprintf(
					'%s[%d] is empty.',
					$k,
					(int) ( $h['index'] ?? 0 )
				);
			}
		}
	}

	// Duplicate text within the same level
	foreach ( [ 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ] as $k ) {
		$seen = [];
		foreach ( (array) ( $details[ $k ] ?? [] ) as $h ) {
			$t = (string) ( $h['text'] ?? '' );
			$t = function_exists( 'mb_strtolower' ) ? mb_strtolower( $t, 'UTF-8' ) : strtolower( $t );
			$seen[ $t ] = ( $seen[ $t ] ?? 0 ) + 1;
		}
		foreach ( $seen as $t => $n ) {
			if ( '' !== $t && $n > 1 ) {
				$issues[] = sprintf(
					'Duplicate %s text "%s" appears %d times.',
					$k,
					$t,
					(int) $n
				);
			}
		}
	}

	// Presence flags requested (placed right after total_headings)
	$h1_present = ( ( $counts['H1'] ?? 0 ) > 0 );
	$h2_present = ( ( $counts['H2'] ?? 0 ) > 0 );
	$h3_present = ( ( $counts['H3'] ?? 0 ) > 0 );

	$response = [
		'summary' => [
			'url'                => $url,
			'total_headings'     => array_sum( $counts ),
			'h1'                 => $h1_present,
			'h2'                 => $h2_present,
			'h3'                 => $h3_present,
			'counts'             => $counts,
			'issues'             => array_values( array_unique( $issues ) ),
			'length_limit_words' => $limit_words,
		],
		'order'   => $flat,
		'details' => $details,
	];

	return rest_ensure_response( $response );
}

/**
 * Fetch helper (kept local to this module)
 */
function myaio_sac_heading_fetch_body( $url ) {
	$res = wp_remote_get(
		$url,
		[
			'timeout'     => 20,
			'redirection' => 5,
			'sslverify'   => true,
			'headers'     => [
				'User-Agent' => 'MYAIO-HeadingAudit/1.0 (+WordPress)',
				'Accept'     => 'text/html,*/*;q=0.8',
			],
		]
	);

	if ( is_wp_error( $res ) ) {
		return [
			'ok'     => false,
			'status' => 0,
			'url'    => $url,
			'body'   => '',
			'error'  => $res->get_error_message(),
		];
	}

	return [
		'ok'     => true,
		'status' => (int) wp_remote_retrieve_response_code( $res ),
		'url'    => $url,
		'body'   => (string) wp_remote_retrieve_body( $res ),
	];
}
