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

namespace Hafenstudios\Kurato\Rest;

use Hafenstudios\Kurato\Db\StageRepository;
use Hafenstudios\Kurato\Insight\FunnelAnalytics;
use Hafenstudios\Kurato\Insight\FunnelFilter;
use Hafenstudios\Kurato\Insight\RecommendationEngine;

defined( 'ABSPATH' ) || exit;

/**
 * REST: die Seekarte des Cockpits (`kurato/v1/funnel` und `kurato/v1/funnel/top`,
 * CP6). Zwei additive, rein lesende Routen, Cap `hs_view_contacts`.
 *
 * Der Auftrag dahinter ist eine Inhaber-Erfahrung: Funnel-Ansichten haben ihn
 * aufgeben lassen, sobald mehr als zwanzig Leute darin standen - nicht wegen der
 * Menge, sondern weil das System bei wachsender Menge keine Meinung hatte. Die
 * beiden Routen beantworten deshalb genau zwei Fragen und sonst nichts:
 *
 * - `GET /funnel`     - **Wo haengt es?** Aggregate je Stufe und Uebergang aus
 *   {@see FunnelAnalytics::report()}. Keine Namen, keine Karten, konstante
 *   Antwortgroesse: eine Zeile je Stufe, egal ob 20 oder 10.000 Kontakte.
 * - `GET /funnel/top` - **Wer ist als Naechstes dran?** Je Stufe hoechstens fuenf
 *   Kontakte mit ihrem Grund, aus {@see RecommendationEngine::urgent_in_stage()}.
 *   Mit `stage_id` wird dieselbe Route zur vollstaendigen, seitenweisen Liste
 *   einer Stufe (Zoom 3) - bewusst dieselbe Route, damit Reede und Hafenliste
 *   nie unterschiedlich filtern oder sortieren koennen.
 *
 * Beide teilen sich den {@see FunnelFilter}; beide geben den Filterzustand und
 * die Zahl der aktiven Filter zurueck, damit die Ansicht sichtbar machen kann,
 * womit gerechnet wurde. **Keine neue Rechenlogik** - dieser Controller ist
 * Auslieferung, die Zahlen kommen unveraendert aus H2.
 */
final class FunnelController extends AbstractController {

	public const REST_NAMESPACE = 'kurato/v1';

	/** Reede (Zoom 2): hoechstens fuenf Kontakte je Stufe. Mehr ist wieder Flut. */
	public const TOP_PER_STAGE = 5;

	/** Hafenliste (Zoom 3): Obergrenze einer Seite, Muster ContactsController. */
	public const MAX_PER_PAGE = 100;

	private FunnelAnalytics $funnel;

	private RecommendationEngine $engine;

	private StageRepository $stages;

	/**
	 * @param FunnelAnalytics      $funnel Funnel-Analytik (H2).
	 * @param RecommendationEngine $engine Regel-Engine (H2).
	 * @param StageRepository      $stages Stufen-Repository.
	 */
	public function __construct( FunnelAnalytics $funnel, RecommendationEngine $engine, StageRepository $stages ) {
		$this->funnel = $funnel;
		$this->engine = $engine;
		$this->stages = $stages;
	}

	/**
	 * Registriert die beiden Routen.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		$filter_args = array(
			'stage_id'     => array( 'type' => 'integer', 'default' => 0 ),
			'tag_id'       => array( 'type' => 'integer', 'default' => 0 ),
			'source'       => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ),
			'overdue'      => array( 'type' => 'string', 'default' => '' ),
			'no_next_step' => array( 'type' => 'string', 'default' => '' ),
			'with_booking' => array( 'type' => 'string', 'default' => '' ),
		);

		register_rest_route(
			self::REST_NAMESPACE,
			'/funnel',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_map' ),
				'permission_callback' => array( $this, 'can_view' ),
				'args'                => array_merge(
					array( 'days' => array( 'type' => 'integer', 'default' => FunnelAnalytics::RANGES[0] ) ),
					$filter_args
				),
			)
		);

		register_rest_route(
			self::REST_NAMESPACE,
			'/funnel/top',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_top' ),
				'permission_callback' => array( $this, 'can_view' ),
				'args'                => array_merge(
					array(
						'page'     => array( 'type' => 'integer', 'default' => 1, 'minimum' => 1 ),
						'per_page' => array( 'type' => 'integer', 'default' => self::TOP_PER_STAGE, 'minimum' => 1, 'maximum' => self::MAX_PER_PAGE ),
						'sort'     => array( 'type' => 'string', 'default' => FunnelFilter::SORTS[0] ),
					),
					$filter_args
				),
			)
		);
	}

	/**
	 * GET /funnel - Zoom 1 "Seekarte": Zahlen und Fluss, keine Namen.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response
	 */
	public function get_map( \WP_REST_Request $request ): \WP_REST_Response {
		$filter = $this->filter_from( $request );
		$days   = FunnelAnalytics::normalize_days( (int) $request->get_param( 'days' ) );

		return new \WP_REST_Response(
			array(
				'funnel'       => $this->funnel->with_filter( $filter )->report( $days ),
				'filter'       => $filter->to_array(),
				'filter_count' => $filter->count_active(),
				'ranges'       => FunnelAnalytics::RANGES,
			),
			200
		);
	}

	/**
	 * GET /funnel/top - Zoom 2 "Reede" (ohne stage_id: fuenf je Stufe) und
	 * Zoom 3 "Hafenliste" (mit stage_id: die volle Liste, seitenweise).
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response
	 */
	public function get_top( \WP_REST_Request $request ): \WP_REST_Response {
		$filter = $this->filter_from( $request );
		$sort   = FunnelFilter::normalize_sort( $request->get_param( 'sort' ) );
		$counts = $this->funnel->with_filter( $filter )->stage_counts();

		$single = $filter->stage_id() > 0;
		$page   = $single ? max( 1, (int) $request->get_param( 'page' ) ) : 1;
		$per    = $single
			? min( self::MAX_PER_PAGE, max( 1, (int) $request->get_param( 'per_page' ) ) )
			: self::TOP_PER_STAGE;
		$offset = ( $page - 1 ) * $per;

		$out = array();
		foreach ( $this->stages->all() as $stage ) {
			$sid = (int) $stage['id'];
			if ( $single && $sid !== $filter->stage_id() ) {
				continue;
			}
			$total = (int) ( $counts[ $sid ] ?? 0 );
			$out[] = array(
				'stage_id' => $sid,
				'label'    => (string) $stage['label'],
				'is_won'   => (bool) $stage['is_won'],
				'is_lost'  => (bool) $stage['is_lost'],
				'total'    => $total,
				// Leere Stufe: gar nicht erst fragen. Spart bei vielen Stufen die
				// Abfragen, die garantiert nichts liefern.
				'items'    => $total > 0 ? $this->engine->urgent_in_stage( $sid, $filter, $per, $offset, $sort ) : array(),
			);
		}

		return new \WP_REST_Response(
			array(
				'stages'       => $out,
				'filter'       => $filter->to_array(),
				'filter_count' => $filter->count_active(),
				'page'         => $page,
				'per_page'     => $per,
				'sort'         => $sort,
				'sorts'        => FunnelFilter::SORTS,
			),
			200
		);
	}

	/**
	 * Liest den Filter aus dem Request.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return FunnelFilter
	 */
	private function filter_from( \WP_REST_Request $request ): FunnelFilter {
		return FunnelFilter::from_params(
			array(
				'stage_id'     => $request->get_param( 'stage_id' ),
				'tag_id'       => $request->get_param( 'tag_id' ),
				'source'       => $request->get_param( 'source' ),
				'overdue'      => $request->get_param( 'overdue' ),
				'no_next_step' => $request->get_param( 'no_next_step' ),
				'with_booking' => $request->get_param( 'with_booking' ),
			)
		);
	}
}
