<?php

namespace GemBoards\Classes\Mention\Base;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

abstract class Mention {
	protected string $type;
	protected string $key = 'zb_mentioned';
	protected string $content;
	protected int $id;
	protected bool $dispatch_hooks = true;

	public function __construct( string $content, int $id, bool $dispatch_hooks = true ) {
		$this->content        = $content;
		$this->id             = $id;
		$this->dispatch_hooks = $dispatch_hooks;
	}

	abstract protected function get_mentioned(): ?array;

	abstract protected function update_mentioned( array $usernames ): void;

	abstract public function rendered( bool $update = false ): ?string;

	protected function get_usernames_as_link( array $usernames ): ?array {
		if ( empty( $usernames ) ) {
			return [];
		}
		/** @var \WP_User[] $users */
		$users = get_users( [
			'login__in' => $usernames,
		] );

		$data = [];
		foreach ( $users as $user ) {
			$data[ $user->user_login ] = sprintf(
				'<Mention id=%d username="%s" display_name="%s" user_email="%s" avatar_url="%s" />',
				$user->ID,
				$user->user_login,
				$user->display_name,
				$user->user_email,
				get_avatar_url( $user->user_email, [ 'size' => 96 ] )
			);
		}

		return $data;
	}

	public function get_rendered_content(): ?string {
		$mentions = $this->extract_mentions( $this->content );

		$links     = $this->get_usernames_as_link( $mentions );
		$usernames = array_keys( $links );

		$newly_mentioned = array_diff( $usernames, $this->get_mentioned() );
		// fire this action when new  username is available, which is not mentioned yet
		if ( $this->dispatch_hooks && ! empty( $newly_mentioned ) ) {
			do_action( "gemboards/$this->type/mentioned", $this->id, $newly_mentioned );
		}

		$removed = array_diff( $this->get_mentioned(), $usernames );
		// fire this action when remove user[s] fropm mention on edit
		if ( $this->dispatch_hooks && ! empty( $removed ) ) {
			do_action( "gemboards/$this->type/mention/remove", $this->id, $removed );
		}

		// Sort usernames by length (descending) to avoid partial matches
		usort( $usernames, fn( $a, $b ) => strlen( $b ) - strlen( $a ) );

		// Create regex pattern dynamically
		$pattern = '/@(' . implode( '|', array_map( 'preg_quote', $usernames ) ) . ')\b/';

		$rendered_content = preg_replace_callback( $pattern, function ( $matches ) use ( $links ) {
			$username = $matches[1];

			return $links[ $username ] ?? $matches[0];
		}, $this->content );

		$this->update_mentioned( $usernames );

		return $rendered_content;
	}

	/**
	 * Extract all @mentions form text contents
	 * Example: @username
	 *
	 * @param string $content
	 *
	 * @return array List of mentioned unique usernames except @
	 */
	protected function extract_mentions( string $content ): array {
		preg_match_all( '/@([a-zA-Z0-9_]+)/', $content, $matches );

		return array_unique( $matches[1] ?? [] );
	}
}
