<?php
/**
 * @package Hafenstudios\Wellenbrecher
 */

namespace Hafenstudios\Wellenbrecher\Pro\Bayes;

use Hafenstudios\Core\Settings\Settings;
use Hafenstudios\Wellenbrecher\Db\TokenRepository;

defined( 'ABSPATH' ) || exit;

/**
 * Lernt den Bayes-Filter AUSSCHLIESSLICH aus Nutzer-Feedback (Technik A6):
 *
 * - transition_comment_status approved -> spam  = Spam-Training (der Betreiber
 *   korrigiert einen durchgelassenen Kommentar).
 * - transition_comment_status spam -> approved  = Ham-Training (Rehabilitation).
 * - Wellenbrecher-Aktion wellenbrecher_quarantine_restored (Leadlotse-
 *   Wiederherstellung) = Ham-Training. Loeschen aus der Quarantaene trainiert
 *   NICHT (Loeschen heisst nicht sicher Spam).
 *
 * "train-on-error, beide Richtungen": trainiert wird nur aus diesen bewussten
 * Korrekturen des Betreibers, in beide Klassen. Bewusst KEIN automatisches
 * Un-Learn (ein Hin-und-Her-Markieren erhoeht dann beide Zaehler = neutral, was
 * die stabilere Wahl gegenueber einem Zaehler-Nulllauf ist). Der Klassifikator
 * wird erst ab 50 Ham + 50 Spam aktiv; vorher wird still weitertrainiert.
 *
 * Die Gesamtzaehler der trainierten Nachrichten liegen in den Settings
 * (pro.bayes.ham_messages / .spam_messages), die je-Token-Zaehler in
 * hs_spam_tokens.
 */
class Trainer {

	/**
	 * Wellenbrecher-interne Aktion, die die Quarantaene-Seite bei erfolgreicher
	 * Wiederherstellung feuert (Ham-Signal).
	 */
	public const RESTORE_ACTION = 'wellenbrecher_quarantine_restored';

	protected TokenRepository $tokens;

	protected Tokenizer $tokenizer;

	protected Settings $settings;

	/**
	 * @param TokenRepository $tokens    Token-Zaehler.
	 * @param Tokenizer       $tokenizer Tokenisierung.
	 * @param Settings        $settings  Wellenbrecher-Settings (Gesamtzaehler).
	 */
	public function __construct( TokenRepository $tokens, Tokenizer $tokenizer, Settings $settings ) {
		$this->tokens    = $tokens;
		$this->tokenizer = $tokenizer;
		$this->settings  = $settings;
	}

	/**
	 * Registriert die Feedback-Hooks (nur bei aktivem Pro-Gate verdrahtet).
	 *
	 * @return void
	 */
	public function register(): void {
		add_action( 'transition_comment_status', array( $this, 'on_comment_transition' ), 10, 3 );
		add_action( self::RESTORE_ACTION, array( $this, 'on_quarantine_restored' ), 10, 1 );
	}

	/**
	 * Kommentar-Feedback: approved<->spam trainiert die jeweilige Klasse.
	 *
	 * @param string $new_status Neuer Status.
	 * @param string $old_status Alter Status.
	 * @param object $comment    Kommentar-Objekt.
	 * @return void
	 */
	public function on_comment_transition( $new_status, $old_status, $comment ): void {
		if ( 'approved' === $old_status && 'spam' === $new_status ) {
			$this->learn( $this->comment_text( $comment ), true );
		} elseif ( 'spam' === $old_status && 'approved' === $new_status ) {
			$this->learn( $this->comment_text( $comment ), false );
		}
	}

	/**
	 * Leadlotse-Wiederherstellung: der Payload war Ham.
	 *
	 * @param mixed $row hs_spam_log-Zeile (mit payload).
	 * @return void
	 */
	public function on_quarantine_restored( $row ): void {
		$row     = is_array( $row ) ? $row : array();
		$payload = json_decode( (string) ( $row['payload'] ?? '' ), true );
		$input   = is_array( $payload ) && is_array( $payload['input'] ?? null ) ? $payload['input'] : array();
		$this->learn( $this->input_text( $input ), false );
	}

	/**
	 * Trainiert einen Text in einer Klasse: Tokens hochzaehlen und den
	 * Gesamtzaehler der Klasse erhoehen.
	 *
	 * @param string $text    Trainingstext.
	 * @param bool   $is_spam true = Spam, false = Ham.
	 * @return void
	 */
	public function learn( string $text, bool $is_spam ): void {
		$tokens = $this->tokenizer->tokenize( $text );
		if ( array() === $tokens ) {
			return;
		}

		$this->tokens->learn( $tokens, $is_spam ? 'spam' : 'ham' );

		$key = $is_spam ? 'pro.bayes.spam_messages' : 'pro.bayes.ham_messages';
		$this->settings->set( $key, (int) $this->settings->get( $key, 0 ) + 1 );
	}

	/**
	 * Trainingstext eines Kommentars (Inhalt + Autor-URL + E-Mail; die E-Mail
	 * wird vom Tokenizer auf die Domain reduziert).
	 *
	 * @param mixed $comment Kommentar-Objekt.
	 * @return string
	 */
	protected function comment_text( $comment ): string {
		if ( ! is_object( $comment ) ) {
			return '';
		}
		return trim(
			(string) ( $comment->comment_content ?? '' ) . ' '
			. (string) ( $comment->comment_author_url ?? '' ) . ' '
			. (string) ( $comment->comment_author_email ?? '' )
		);
	}

	/**
	 * Trainingstext aus einem Leadlotse-Submit-Payload: alle Feldwerte ausser den
	 * technischen Signal-/Meta-Feldern (Praefix wb_ / ll_).
	 *
	 * @param array<string, mixed> $input Roh-Submit.
	 * @return string
	 */
	protected function input_text( array $input ): string {
		$parts = array();
		foreach ( $input as $key => $value ) {
			$key = (string) $key;
			if ( 0 === strpos( $key, 'wb_' ) || 0 === strpos( $key, 'll_' ) ) {
				continue;
			}
			if ( is_array( $value ) ) {
				$parts[] = implode( ' ', array_map( 'strval', $value ) );
			} elseif ( is_scalar( $value ) ) {
				$parts[] = (string) $value;
			}
		}
		return trim( implode( ' ', $parts ) );
	}
}
