<?php
/**
 * Link analyzer.
 *
 * Analyzes internal and external links in post content.
 *
 * @package MonitorCat\Modules\Seo\Analyzer
 */

declare(strict_types=1);

namespace MonitorCat\Modules\Seo\Analyzer;

defined( 'ABSPATH' ) || exit;

/**
 * Analyzes links in post content for SEO best practices.
 */
final class LinkAnalyzer {

	/**
	 * Run the link analysis on a post.
	 *
	 * @param int $post_id The post ID to analyze.
	 * @return array{score: int, status: string, checks: array<int, array{label: string, status: string, message: string, value: string|null}>}
	 */
	public function analyze( int $post_id ): array {
		$content = get_post_field( 'post_content', $post_id );
		$content = is_string( $content ) ? $content : '';
		$links   = $this->parse_links( $content );
		$checks  = array();

		$checks[] = $this->check_link_count( $links );
		$checks[] = $this->check_internal_links( $links );
		$checks[] = $this->check_external_links( $links );
		$checks[] = $this->check_broken_hrefs( $links );

		$score  = $this->calculate_score( $checks );
		$status = $this->determine_status( $score );

		return array(
			'score'  => $score,
			'status' => $status,
			'checks' => $checks,
		);
	}

	/**
	 * Parse all links from content.
	 *
	 * @param string $content The post content.
	 * @return array{total: int, internal: int, external: int, broken: int, links: array<int, array{href: string, type: string, text: string}>}
	 */
	private function parse_links( string $content ): array {
		$result = array(
			'total'    => 0,
			'internal' => 0,
			'external' => 0,
			'broken'   => 0,
			'links'    => array(),
		);

		if ( ! preg_match_all( '/<a\s[^>]*href=["\']([^"\']*)["\'][^>]*>(.*?)<\/a>/is', $content, $matches, PREG_SET_ORDER ) ) {
			return $result;
		}

		$home_host = wp_parse_url( home_url(), PHP_URL_HOST );

		foreach ( $matches as $match ) {
			$href = trim( $match[1] );
			$text = wp_strip_all_tags( $match[2] );

			++$result['total'];

			if ( empty( $href ) || '#' === $href ) {
				++$result['broken'];
				$result['links'][] = array(
					'href' => $href,
					'type' => 'broken',
					'text' => $text,
				);
				continue;
			}

			$link_host = wp_parse_url( $href, PHP_URL_HOST );

			if ( null === $link_host || '' === $link_host || $link_host === $home_host ) {
				++$result['internal'];
				$result['links'][] = array(
					'href' => $href,
					'type' => 'internal',
					'text' => $text,
				);
			} else {
				++$result['external'];
				$result['links'][] = array(
					'href' => $href,
					'type' => 'external',
					'text' => $text,
				);
			}
		}

		return $result;
	}

	/**
	 * Check overall link count.
	 *
	 * @param array{total: int, internal: int, external: int, broken: int, links: array<int, array{href: string, type: string, text: string}>} $links Parsed link data.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_link_count( array $links ): array {
		if ( 0 === $links['total'] ) {
			return array(
				'label'   => __( 'Link Count', 'monitor-cat' ),
				'status'  => 'warning',
				'message' => __( 'No links found in content. Adding internal and external links improves SEO.', 'monitor-cat' ),
				'value'   => '0',
			);
		}

		return array(
			'label'   => __( 'Link Count', 'monitor-cat' ),
			'status'  => 'pass',
			/* translators: 1: total links, 2: internal count, 3: external count */
			'message' => sprintf( __( 'Found %1$d link(s): %2$d internal, %3$d external.', 'monitor-cat' ), $links['total'], $links['internal'], $links['external'] ),
			'value'   => (string) $links['total'],
		);
	}

	/**
	 * Check for internal links.
	 *
	 * @param array{total: int, internal: int, external: int, broken: int, links: array<int, array{href: string, type: string, text: string}>} $links Parsed link data.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_internal_links( array $links ): array {
		if ( $links['internal'] > 0 ) {
			return array(
				'label'   => __( 'Internal Links', 'monitor-cat' ),
				'status'  => 'pass',
				/* translators: %d: number of internal links */
				'message' => sprintf( __( 'Found %d internal link(s). Internal linking helps SEO and user navigation.', 'monitor-cat' ), $links['internal'] ),
				'value'   => (string) $links['internal'],
			);
		}

		return array(
			'label'   => __( 'Internal Links', 'monitor-cat' ),
			'status'  => 'warning',
			'message' => __( 'No internal links found. Add links to other content on your site to improve SEO.', 'monitor-cat' ),
			'value'   => '0',
		);
	}

	/**
	 * Check external links and nofollow usage.
	 *
	 * @param array{total: int, internal: int, external: int, broken: int, links: array<int, array{href: string, type: string, text: string}>} $links Parsed link data.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_external_links( array $links ): array {
		if ( 0 === $links['external'] ) {
			return array(
				'label'   => __( 'External Links', 'monitor-cat' ),
				'status'  => 'pass',
				'message' => __( 'No external links found. Consider linking to authoritative sources when relevant.', 'monitor-cat' ),
				'value'   => '0',
			);
		}

		return array(
			'label'   => __( 'External Links', 'monitor-cat' ),
			'status'  => 'pass',
			/* translators: %d: number of external links */
			'message' => sprintf( __( 'Found %d external link(s). Consider using rel="nofollow" for untrusted or sponsored links.', 'monitor-cat' ), $links['external'] ),
			'value'   => (string) $links['external'],
		);
	}

	/**
	 * Check for broken or empty hrefs.
	 *
	 * @param array{total: int, internal: int, external: int, broken: int, links: array<int, array{href: string, type: string, text: string}>} $links Parsed link data.
	 * @return array{label: string, status: string, message: string, value: string|null}
	 */
	private function check_broken_hrefs( array $links ): array {
		if ( 0 === $links['broken'] ) {
			return array(
				'label'   => __( 'Broken Links', 'monitor-cat' ),
				'status'  => 'pass',
				'message' => __( 'No empty or anchor-only (#) links found.', 'monitor-cat' ),
				'value'   => '0',
			);
		}

		return array(
			'label'   => __( 'Broken Links', 'monitor-cat' ),
			'status'  => 'fail',
			/* translators: %d: number of broken links */
			'message' => sprintf( __( 'Found %d link(s) with empty or anchor-only href. Fix these links.', 'monitor-cat' ), $links['broken'] ),
			'value'   => (string) $links['broken'],
		);
	}

	/**
	 * Calculate weighted score from checks.
	 *
	 * @param array<int, array{label: string, status: string, message: string, value: string|null}> $checks The checks array.
	 * @return int Score from 0 to 100.
	 */
	private function calculate_score( array $checks ): int {
		if ( empty( $checks ) ) {
			return 0;
		}

		$weights = array( 20, 30, 20, 30 );
		$total   = 0;

		foreach ( $checks as $index => $check ) {
			$weight     = $weights[ $index ] ?? 25;
			$multiplier = 'pass' === $check['status'] ? 1.0 : ( 'warning' === $check['status'] ? 0.5 : 0.0 );
			$total     += $weight * $multiplier;
		}

		return (int) round( $total );
	}

	/**
	 * Determine overall status from score.
	 *
	 * @param int $score The calculated score.
	 * @return string 'pass', 'warning', or 'fail'.
	 */
	private function determine_status( int $score ): string {
		if ( $score >= 80 ) {
			return 'pass';
		}

		if ( $score >= 50 ) {
			return 'warning';
		}

		return 'fail';
	}
}
