<?php
/**
 * WordPress core integrity detector.
 *
 * @package LocalGuardScanner
 */

namespace LocalGuardScanner\Detection\Integrity;

use LocalGuardScanner\Domain\Detection\Detector;
use LocalGuardScanner\Domain\Detection\Evidence;
use LocalGuardScanner\Domain\Detection\ScannableObject;

/**
 * Compares core files against an exact offline SHA-256 manifest.
 */
final class CoreIntegrityDetector implements Detector {
	/** @var array<string,string> */
	private $manifest;

	/** @param array<string,string> $manifest Expected hashes. */
	public function __construct( array $manifest ) {
		$this->manifest = $manifest;
	}

	/** @return string */
	public function id() {
		return 'core.integrity';
	}

	/** @inheritDoc */
	public function inspect( ScannableObject $scanned_object ) {
		if ( 'file' !== $scanned_object->type() ) {
			return array();
		}
		$locator = ltrim( str_replace( '\\', '/', $scanned_object->locator() ), '/' );
		if ( ! $this->is_core_path( $locator ) ) {
			return array();
		}
		$attributes = $scanned_object->attributes();
		$actual     = isset( $attributes['sha256'] ) && is_string( $attributes['sha256'] ) ? $attributes['sha256'] : '';
		if ( isset( $this->manifest[ $locator ] ) && ! hash_equals( $this->manifest[ $locator ], $actual ) ) {
			return array( new Evidence( 'core.hash_mismatch', 82, 98, 'wordpress_core_file_modified', $locator ) );
		}
		if ( ! isset( $this->manifest[ $locator ] ) && ( 0 === strpos( $locator, 'wp-admin/' ) || 0 === strpos( $locator, 'wp-includes/' ) ) ) {
			return array( new Evidence( 'core.unexpected_file', 62, 84, 'unexpected_file_in_wordpress_core', $locator ) );
		}
		return array();
	}

	/**
	 * Return canonical files that were not observed.
	 *
	 * @param string[] $seen Seen locators.
	 * @return string[]
	 */
	public function missing_paths( array $seen ) {
		$seen_lookup = array_fill_keys( $seen, true );
		return array_values( array_diff( array_keys( $this->manifest ), array_keys( $seen_lookup ) ) );
	}

	/** @return bool */
	private function is_core_path( string $locator ): bool {
		if ( 0 === strpos( $locator, 'wp-admin/' ) || 0 === strpos( $locator, 'wp-includes/' ) ) {
			return true;
		}
		return false === strpos( $locator, '/' ) && isset( $this->manifest[ $locator ] );
	}
}
