<?php
/**
 * Stream-type mapping and normalization.
 *
 * @package DataStream
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class DataStream_Chronicles
 *
 * Resolves the stream type and source label for each ingested record. The
 * configured stream name wins; otherwise the record's own type field is used
 * (any slug is allowed), with shape-based detection as a last resort.
 */
class DataStream_Chronicles {

	/**
	 * Well-known stream types with shape-based detection.
	 */
	public const STREAM_AUDIT     = 'audit_log';
	public const STREAM_TELEMETRY = 'sensor_telemetry';
	public const STREAM_GENERIC   = 'generic';

	/**
	 * Resolve stream metadata from an API item.
	 *
	 * @param array<string, mixed> $item     Raw API record.
	 * @param string               $override Configured stream name; when non-empty
	 *                                       it is applied to every record.
	 * @return array{stream_type: string, source: string}
	 */
	public static function resolve_stream_meta( $item, $override = '' ) {
		$override = sanitize_key( (string) $override );

		if ( '' !== $override ) {
			$stream_type = $override;
		} elseif ( ! empty( $item['stream_type'] ) && is_scalar( $item['stream_type'] ) ) {
			$stream_type = self::normalize_stream_type( (string) $item['stream_type'] );
		} elseif ( ! empty( $item['type'] ) && is_scalar( $item['type'] ) ) {
			$stream_type = self::normalize_stream_type( (string) $item['type'] );
		} elseif ( self::is_audit_record( $item ) ) {
			$stream_type = self::STREAM_AUDIT;
		} elseif ( self::is_telemetry_record( $item ) ) {
			$stream_type = self::STREAM_TELEMETRY;
		} else {
			$stream_type = self::STREAM_GENERIC;
		}

		$source = '';

		if ( ! empty( $item['source'] ) && is_scalar( $item['source'] ) ) {
			$source = sanitize_text_field( (string) $item['source'] );
		} elseif ( ! empty( $item['sensor_id'] ) && is_scalar( $item['sensor_id'] ) ) {
			$source = sanitize_text_field( (string) $item['sensor_id'] );
		} elseif ( ! empty( $item['actor'] ) && is_scalar( $item['actor'] ) ) {
			$source = sanitize_text_field( (string) $item['actor'] );
		}

		/**
		 * Filter resolved stream metadata before persistence.
		 *
		 * @param array{stream_type: string, source: string} $meta  Resolved metadata.
		 * @param array<string, mixed>                       $item  Raw API item.
		 */
		return apply_filters(
			'datastream_chronicles_map_item',
			array(
				'stream_type' => $stream_type,
				'source'      => $source,
			),
			$item
		);
	}

	/**
	 * Normalize a stream type slug. Any slug is valid; the audit/telemetry
	 * aliases collapse to their canonical names.
	 *
	 * @param string $type Raw type value.
	 * @return string
	 */
	private static function normalize_stream_type( $type ) {
		$type = sanitize_key( (string) $type );

		if ( '' === $type ) {
			return self::STREAM_GENERIC;
		}

		if ( in_array( $type, array( 'audit', 'audit_log' ), true ) ) {
			return self::STREAM_AUDIT;
		}

		if ( in_array( $type, array( 'telemetry', 'sensor', 'sensor_telemetry' ), true ) ) {
			return self::STREAM_TELEMETRY;
		}

		return $type;
	}

	/**
	 * Detect audit-log shaped records.
	 *
	 * @param array<string, mixed> $item API item.
	 * @return bool
	 */
	private static function is_audit_record( $item ) {
		return isset( $item['action'], $item['actor'] ) || isset( $item['event'], $item['timestamp'] );
	}

	/**
	 * Detect sensor telemetry shaped records.
	 *
	 * @param array<string, mixed> $item API item.
	 * @return bool
	 */
	private static function is_telemetry_record( $item ) {
		return isset( $item['sensor_id'], $item['reading'] )
			|| isset( $item['metric'], $item['value'] );
	}
}
