<?php
/**
 * REST API controller.
 *
 * @package MerchantKits\AttendanceManager
 */

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

/**
 * Registers and handles all mkam/v1 REST routes.
 */
class MKAM_REST_API {

	/**
	 * REST namespace.
	 *
	 * @var string
	 */
	const NS = 'mkam/v1';

	/**
	 * Register all routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		$admin = array( $this, 'check_admin' );
		$auth  = array( $this, 'check_auth' );

		register_rest_route(
			self::NS,
			'/employees',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'list_employees' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_employee' ),
					'permission_callback' => $admin,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/employees/(?P<id>\d+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_employee' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_employee' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_employee' ),
					'permission_callback' => $admin,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/employees/(?P<id>\d+)/attendance',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'employee_attendance' ),
				'permission_callback' => array( $this, 'check_admin_or_self' ),
			)
		);

		register_rest_route(
			self::NS,
			'/employees/(?P<id>\d+)/leaves',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'employee_leaves' ),
				'permission_callback' => array( $this, 'check_admin_or_self' ),
			)
		);

		register_rest_route(
			self::NS,
			'/employees/(?P<id>\d+)/leave-balance',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'employee_leave_balance' ),
				'permission_callback' => array( $this, 'check_admin_or_self' ),
			)
		);

		register_rest_route(
			self::NS,
			'/attendance/clock-in',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'clock_in' ),
				'permission_callback' => $auth,
			)
		);

		register_rest_route(
			self::NS,
			'/attendance/clock-out',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'clock_out' ),
				'permission_callback' => $auth,
			)
		);

		register_rest_route(
			self::NS,
			'/attendance',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'list_attendance' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_attendance' ),
					'permission_callback' => $admin,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/attendance/(?P<id>\d+)',
			array(
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_attendance' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_attendance' ),
					'permission_callback' => $admin,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/leaves',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'list_leaves' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_leave' ),
					'permission_callback' => $auth,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/leaves/(?P<id>\d+)/approve',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'approve_leave' ),
				'permission_callback' => $admin,
			)
		);

		register_rest_route(
			self::NS,
			'/leaves/(?P<id>\d+)/reject',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'reject_leave' ),
				'permission_callback' => $admin,
			)
		);

		register_rest_route(
			self::NS,
			'/leaves/(?P<id>\d+)',
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'delete_leave' ),
				'permission_callback' => $admin,
			)
		);

		register_rest_route(
			self::NS,
			'/shifts',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'list_shifts' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_shift' ),
					'permission_callback' => $admin,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/shifts/(?P<id>\d+)',
			array(
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_shift' ),
					'permission_callback' => $admin,
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_shift' ),
					'permission_callback' => $admin,
				),
			)
		);

		register_rest_route(
			self::NS,
			'/stats/dashboard',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'dashboard_stats' ),
				'permission_callback' => $admin,
			)
		);

		register_rest_route(
			self::NS,
			'/stats/monthly/(?P<year>\d{4})/(?P<month>\d{1,2})',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'monthly_stats' ),
				'permission_callback' => $admin,
			)
		);

		register_rest_route(
			self::NS,
			'/ai/summary',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'ai_summary' ),
				'permission_callback' => $admin,
			)
		);

		register_rest_route(
			self::NS,
			'/ai/patterns',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'ai_patterns' ),
				'permission_callback' => $admin,
			)
		);
	}

	/* ---------------------------------------------------------------------
	 * Permission callbacks
	 * ------------------------------------------------------------------- */

	/**
	 * Admin permission check.
	 *
	 * @return bool
	 */
	public function check_admin() {
		return current_user_can( MKAM_Core::admin_capability() );
	}

	/**
	 * Authenticated employee permission check.
	 *
	 * @return bool
	 */
	public function check_auth() {
		if ( ! is_user_logged_in() ) {
			return false;
		}

		$employee = MKAM_Employee::get_by_user( get_current_user_id() );

		return $employee && 'active' === $employee['status'];
	}

	/**
	 * Admin-or-self permission check for employee-scoped routes.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return bool
	 */
	public function check_admin_or_self( $request ) {
		if ( $this->check_admin() ) {
			return true;
		}

		if ( ! is_user_logged_in() ) {
			return false;
		}

		$employee = MKAM_Employee::get_by_user( get_current_user_id() );

		return $employee && (int) $employee['id'] === (int) $request['id'];
	}

	/* ---------------------------------------------------------------------
	 * Employees
	 * ------------------------------------------------------------------- */

	/**
	 * List employees with pagination headers.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function list_employees( $request ) {
		$per_page = $request->get_param( 'per_page' ) ? absint( $request->get_param( 'per_page' ) ) : 20;
		$page     = $request->get_param( 'page' ) ? absint( $request->get_param( 'page' ) ) : 1;

		$args = array(
			'status'     => $request->get_param( 'status' ) ? sanitize_text_field( $request->get_param( 'status' ) ) : '',
			'department' => $request->get_param( 'department' ) ? sanitize_text_field( $request->get_param( 'department' ) ) : '',
			'search'     => $request->get_param( 'search' ) ? sanitize_text_field( $request->get_param( 'search' ) ) : '',
			'per_page'   => $per_page,
			'page'       => $page,
		);

		$items = MKAM_Employee::query( $args );
		$total = MKAM_Employee::count( $args );

		return $this->paginated_response( $items, $total, $per_page, 'employees' );
	}

	/**
	 * Get a single employee.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_employee( $request ) {
		$employee = MKAM_Employee::get( absint( $request['id'] ) );

		if ( ! $employee ) {
			return new WP_Error( 'mkam_not_found', __( 'Employee not found.', 'merchantkits-attendance-manager' ), array( 'status' => 404 ) );
		}

		return $this->respond( $employee, 'employees' );
	}

	/**
	 * Create an employee.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function create_employee( $request ) {
		$result = MKAM_Employee::create( (array) $request->get_json_params() );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( MKAM_Employee::get( $result ), 'employees', 201 );
	}

	/**
	 * Update an employee.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function update_employee( $request ) {
		$id     = absint( $request['id'] );
		$result = MKAM_Employee::update( $id, (array) $request->get_json_params() );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( MKAM_Employee::get( $id ), 'employees' );
	}

	/**
	 * Delete an employee.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function delete_employee( $request ) {
		$deleted = MKAM_Employee::delete( absint( $request['id'] ) );

		return new WP_REST_Response( array( 'deleted' => $deleted ), $deleted ? 200 : 404 );
	}

	/**
	 * Get an employee's attendance records.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function employee_attendance( $request ) {
		$items = MKAM_Attendance::query(
			array(
				'employee_id' => absint( $request['id'] ),
				'date_from'   => $request->get_param( 'date_from' ) ? sanitize_text_field( $request->get_param( 'date_from' ) ) : '',
				'date_to'     => $request->get_param( 'date_to' ) ? sanitize_text_field( $request->get_param( 'date_to' ) ) : '',
			)
		);

		return $this->respond( $items, 'attendance' );
	}

	/**
	 * Get an employee's leave records.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function employee_leaves( $request ) {
		$items = MKAM_Leave::query( array( 'employee_id' => absint( $request['id'] ) ) );

		return $this->respond( $items, 'leaves' );
	}

	/**
	 * Get an employee's leave balances across all policies.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function employee_leave_balance( $request ) {
		$id        = absint( $request['id'] );
		$balances  = array();

		foreach ( MKAM_Leave::get_policies() as $policy ) {
			$balances[ $policy['leave_type'] ] = MKAM_Leave::get_balance( $id, $policy['leave_type'] );
		}

		return $this->respond( $balances, 'leave-balance' );
	}

	/* ---------------------------------------------------------------------
	 * Attendance
	 * ------------------------------------------------------------------- */

	/**
	 * Clock in the current user's employee.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function clock_in() {
		$employee = MKAM_Employee::get_by_user( get_current_user_id() );
		$today    = current_time( 'Y-m-d' );

		if ( MKAM_Attendance::get_open( $employee['id'] ) ) {
			return new WP_Error( 'mkam_already_clocked_in', __( 'You are already clocked in.', 'merchantkits-attendance-manager' ), array( 'status' => 409 ) );
		}

		$now   = current_time( 'mysql' );
		$shift = $employee['shift_id'] ? MKAM_Shift::get( $employee['shift_id'] ) : array();
		$late  = MKAM_Attendance::calculate_late_minutes( $now, (array) $shift );

		$id = MKAM_Attendance::create(
			array(
				'employee_id'  => $employee['id'],
				'date'         => $today,
				'clock_in'     => $now,
				'clock_in_ip'  => MKAM_Attendance::get_client_ip(),
				'status'       => $late > 0 ? 'late' : 'present',
				'late_minutes' => $late,
			)
		);

		if ( is_wp_error( $id ) ) {
			return $this->error_response( $id );
		}

		do_action( 'mkam_clock_in', $id, $employee['id'] );

		return $this->respond( MKAM_Attendance::get( $id ), 'attendance', 201 );
	}

	/**
	 * Clock out the current user's employee.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function clock_out() {
		$employee = MKAM_Employee::get_by_user( get_current_user_id() );
		$open     = MKAM_Attendance::get_open( $employee['id'] );

		if ( ! $open ) {
			return new WP_Error( 'mkam_not_clocked_in', __( 'You are not currently clocked in.', 'merchantkits-attendance-manager' ), array( 'status' => 409 ) );
		}

		$now   = current_time( 'mysql' );
		$shift = $employee['shift_id'] ? MKAM_Shift::get( $employee['shift_id'] ) : array();
		$hours = MKAM_Attendance::calculate_hours( $open['clock_in'], $now, (array) $shift );

		$result = MKAM_Attendance::update(
			$open['id'],
			array(
				'clock_out'      => $now,
				'clock_out_ip'   => MKAM_Attendance::get_client_ip(),
				'total_hours'    => $hours['total_hours'],
				'overtime_hours' => $hours['overtime_hours'],
			)
		);

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		do_action( 'mkam_clock_out', (int) $open['id'], (int) $employee['id'] );

		return $this->respond( MKAM_Attendance::get( $open['id'] ), 'attendance' );
	}

	/**
	 * List attendance records with pagination headers.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function list_attendance( $request ) {
		$per_page = $request->get_param( 'per_page' ) ? absint( $request->get_param( 'per_page' ) ) : 20;
		$page     = $request->get_param( 'page' ) ? absint( $request->get_param( 'page' ) ) : 1;

		$args = array(
			'employee_id' => $request->get_param( 'employee_id' ) ? absint( $request->get_param( 'employee_id' ) ) : 0,
			'status'      => $request->get_param( 'status' ) ? sanitize_text_field( $request->get_param( 'status' ) ) : '',
			'date_from'   => $request->get_param( 'date_from' ) ? sanitize_text_field( $request->get_param( 'date_from' ) ) : '',
			'date_to'     => $request->get_param( 'date_to' ) ? sanitize_text_field( $request->get_param( 'date_to' ) ) : '',
		);

		$total = count( MKAM_Attendance::query( $args ) );
		$items = MKAM_Attendance::query( array_merge( $args, array( 'per_page' => $per_page, 'page' => $page ) ) );

		return $this->paginated_response( $items, $total, $per_page, 'attendance' );
	}

	/**
	 * Create a manual attendance record.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function create_attendance( $request ) {
		$data = (array) $request->get_json_params();

		$data['is_manual_entry'] = 1;
		$data['approved_by']     = get_current_user_id();

		$id = MKAM_Attendance::create( $data );

		if ( is_wp_error( $id ) ) {
			return $this->error_response( $id );
		}

		do_action( 'mkam_attendance_manual_entry', $id, get_current_user_id() );

		return $this->respond( MKAM_Attendance::get( $id ), 'attendance', 201 );
	}

	/**
	 * Update an attendance record.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function update_attendance( $request ) {
		$id     = absint( $request['id'] );
		$result = MKAM_Attendance::update( $id, (array) $request->get_json_params() );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( MKAM_Attendance::get( $id ), 'attendance' );
	}

	/**
	 * Delete an attendance record.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function delete_attendance( $request ) {
		$deleted = MKAM_Attendance::delete( absint( $request['id'] ) );

		return new WP_REST_Response( array( 'deleted' => $deleted ), $deleted ? 200 : 404 );
	}

	/* ---------------------------------------------------------------------
	 * Leaves
	 * ------------------------------------------------------------------- */

	/**
	 * List leave requests with pagination headers.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function list_leaves( $request ) {
		$per_page = $request->get_param( 'per_page' ) ? absint( $request->get_param( 'per_page' ) ) : 20;
		$page     = $request->get_param( 'page' ) ? absint( $request->get_param( 'page' ) ) : 1;

		$args = array(
			'employee_id' => $request->get_param( 'employee_id' ) ? absint( $request->get_param( 'employee_id' ) ) : 0,
			'status'      => $request->get_param( 'status' ) ? sanitize_text_field( $request->get_param( 'status' ) ) : '',
			'leave_type'  => $request->get_param( 'leave_type' ) ? sanitize_text_field( $request->get_param( 'leave_type' ) ) : '',
		);

		$total = count( MKAM_Leave::query( $args ) );
		$items = MKAM_Leave::query( array_merge( $args, array( 'per_page' => $per_page, 'page' => $page ) ) );

		return $this->paginated_response( $items, $total, $per_page, 'leaves' );
	}

	/**
	 * Submit a leave request for the current employee.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function create_leave( $request ) {
		$employee = MKAM_Employee::get_by_user( get_current_user_id() );
		$data     = (array) $request->get_json_params();

		// Admins may submit on behalf of an employee; others are forced to self.
		if ( ! $this->check_admin() || empty( $data['employee_id'] ) ) {
			$data['employee_id'] = $employee ? $employee['id'] : 0;
		}

		$id = MKAM_Leave::create( $data );

		if ( is_wp_error( $id ) ) {
			return $this->error_response( $id );
		}

		return $this->respond( MKAM_Leave::get( $id ), 'leaves', 201 );
	}

	/**
	 * Approve a leave request.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function approve_leave( $request ) {
		$id     = absint( $request['id'] );
		$note   = $request->get_param( 'note' ) ? sanitize_textarea_field( $request->get_param( 'note' ) ) : '';
		$result = MKAM_Leave::approve( $id, get_current_user_id(), $note );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( MKAM_Leave::get( $id ), 'leaves' );
	}

	/**
	 * Reject a leave request.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function reject_leave( $request ) {
		$id     = absint( $request['id'] );
		$note   = $request->get_param( 'note' ) ? sanitize_textarea_field( $request->get_param( 'note' ) ) : '';
		$result = MKAM_Leave::reject( $id, get_current_user_id(), $note );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( MKAM_Leave::get( $id ), 'leaves' );
	}

	/**
	 * Delete a leave request.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function delete_leave( $request ) {
		$deleted = MKAM_Leave::delete( absint( $request['id'] ) );

		return new WP_REST_Response( array( 'deleted' => $deleted ), $deleted ? 200 : 404 );
	}

	/* ---------------------------------------------------------------------
	 * Shifts
	 * ------------------------------------------------------------------- */

	/**
	 * List shifts.
	 *
	 * @return WP_REST_Response
	 */
	public function list_shifts() {
		return $this->respond( MKAM_Shift::all(), 'shifts' );
	}

	/**
	 * Create a shift.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function create_shift( $request ) {
		$id = MKAM_Shift::create( (array) $request->get_json_params() );

		if ( is_wp_error( $id ) ) {
			return $this->error_response( $id );
		}

		return $this->respond( MKAM_Shift::get( $id ), 'shifts', 201 );
	}

	/**
	 * Update a shift.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function update_shift( $request ) {
		$id     = absint( $request['id'] );
		$result = MKAM_Shift::update( $id, (array) $request->get_json_params() );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( MKAM_Shift::get( $id ), 'shifts' );
	}

	/**
	 * Delete a shift.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function delete_shift( $request ) {
		$deleted = MKAM_Shift::delete( absint( $request['id'] ) );

		return new WP_REST_Response( array( 'deleted' => $deleted ), $deleted ? 200 : 404 );
	}

	/* ---------------------------------------------------------------------
	 * Stats & AI
	 * ------------------------------------------------------------------- */

	/**
	 * Dashboard statistics.
	 *
	 * @return WP_REST_Response
	 */
	public function dashboard_stats() {
		global $wpdb;

		$today       = current_time( 'Y-m-d' );
		$emp_table   = MKAM_Employee::table();
		$att_table   = MKAM_Attendance::table();
		$leave_table = MKAM_Leave::table();

		// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$total   = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$emp_table} WHERE status = 'active'" );
		$present = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$att_table} WHERE date = %s AND clock_in IS NOT NULL", $today ) );
		$leave   = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$leave_table} WHERE status = 'approved' AND start_date <= %s AND end_date >= %s", $today, $today ) );
		$late    = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$att_table} WHERE date = %s AND status = 'late'", $today ) );
		$pending = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$leave_table} WHERE status = 'pending'" );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		$stats = apply_filters(
			'mkam_dashboard_stats',
			array(
				'total_employees' => $total,
				'present_today'   => $present,
				'on_leave_today'  => $leave,
				'absent_today'    => max( 0, $total - $present - $leave ),
				'late_today'      => $late,
				'pending_leaves'  => $pending,
			)
		);

		return $this->respond( $stats, 'stats/dashboard' );
	}

	/**
	 * Monthly report data.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function monthly_stats( $request ) {
		$year  = absint( $request['year'] );
		$month = min( 12, max( 1, absint( $request['month'] ) ) );
		$from  = sprintf( '%04d-%02d-01', $year, $month );
		$to    = gmdate( 'Y-m-t', strtotime( $from ) );

		$records = MKAM_Attendance::query(
			array(
				'date_from' => $from,
				'date_to'   => $to,
			)
		);

		return $this->respond(
			array(
				'year'    => $year,
				'month'   => $month,
				'records' => $records,
			),
			'stats/monthly'
		);
	}

	/**
	 * Generate an AI attendance summary.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function ai_summary( $request ) {
		if ( ! class_exists( 'MKAM_AI_Insights' ) ) {
			return new WP_Error( 'mkam_ai_unavailable', __( 'AI insights are unavailable.', 'merchantkits-attendance-manager' ), array( 'status' => 501 ) );
		}

		$year  = $request->get_param( 'year' ) ? absint( $request->get_param( 'year' ) ) : (int) current_time( 'Y' );
		$month = $request->get_param( 'month' ) ? absint( $request->get_param( 'month' ) ) : (int) current_time( 'n' );

		$ai     = new MKAM_AI_Insights();
		$result = $ai->monthly_summary( $year, $month );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( array( 'summary' => $result ), 'ai/summary' );
	}

	/**
	 * Detect AI absence patterns.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function ai_patterns( $request ) {
		if ( ! class_exists( 'MKAM_AI_Insights' ) ) {
			return new WP_Error( 'mkam_ai_unavailable', __( 'AI insights are unavailable.', 'merchantkits-attendance-manager' ), array( 'status' => 501 ) );
		}

		$from = $request->get_param( 'date_from' ) ? sanitize_text_field( $request->get_param( 'date_from' ) ) : gmdate( 'Y-m-01' );
		$to   = $request->get_param( 'date_to' ) ? sanitize_text_field( $request->get_param( 'date_to' ) ) : gmdate( 'Y-m-t' );

		$ai     = new MKAM_AI_Insights();
		$result = $ai->detect_patterns( $from, $to );

		if ( is_wp_error( $result ) ) {
			return $this->error_response( $result );
		}

		return $this->respond( array( 'patterns' => $result ), 'ai/patterns' );
	}

	/* ---------------------------------------------------------------------
	 * Response helpers
	 * ------------------------------------------------------------------- */

	/**
	 * Build a filtered REST response.
	 *
	 * @param mixed  $data     Payload.
	 * @param string $endpoint Endpoint identifier for filtering.
	 * @param int    $status   HTTP status code.
	 * @return WP_REST_Response
	 */
	private function respond( $data, $endpoint, $status = 200 ) {
		$data = apply_filters( 'mkam_rest_response', $data, $endpoint );

		return new WP_REST_Response( $data, $status );
	}

	/**
	 * Build a list response with pagination headers.
	 *
	 * @param array  $items    Items.
	 * @param int    $total    Total item count.
	 * @param int    $per_page Items per page.
	 * @param string $endpoint Endpoint identifier.
	 * @return WP_REST_Response
	 */
	private function paginated_response( $items, $total, $per_page, $endpoint ) {
		$response = $this->respond( $items, $endpoint );
		$response->header( 'X-WP-Total', (int) $total );
		$response->header( 'X-WP-TotalPages', (int) ceil( $total / max( 1, $per_page ) ) );

		return $response;
	}

	/**
	 * Convert a WP_Error into a REST error response with a status code.
	 *
	 * @param WP_Error $error Error object.
	 * @return WP_Error
	 */
	private function error_response( $error ) {
		return new WP_Error(
			$error->get_error_code(),
			$error->get_error_message(),
			array( 'status' => 400 )
		);
	}
}
