<?php
/**
 * File Path: includes/data/class-mbs-pos-location-dao.php
 *
 * MBS_POS_Location_DAO -- CRUD for mbs_pos_locations
 *
 * Manages physical POS locations used by OpenPOS and similar in-person sales
 * systems. All writes use $wpdb->insert / $wpdb->update / $wpdb->delete with
 * WordPress-standard placeholder escaping.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Data
 * @since      1.2.0
 */

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

// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- DAO/migration: table names via $wpdb->prefix are trusted server-controlled values, not user input
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter -- same reason: $wpdb->prefix table names are safe; $where_sql is built from sanitized server-controlled inputs
class MBS_POS_Location_DAO { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Return all locations, optionally filtered to active only.
	 *
	 * @param bool $active_only When true, only rows with is_active = 1 are returned.
	 * @return object[]
	 */
	public static function get_all( bool $active_only = false ): array {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_pos_locations';
		if ( $active_only ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE is_active = 1 ORDER BY name ASC" ) ) ?: array(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
		}
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		return $wpdb->get_results( "SELECT * FROM {$table} ORDER BY name ASC" ) ?: array(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
	}

	/**
	 * Fetch a single location by ID.
	 *
	 * @param int $id Location ID.
	 * @return object|null
	 */
	public static function get( int $id ): ?object {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_pos_locations';
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
		return $row ?: null;
	}

	/**
	 * Insert a new location.
	 *
	 * @param string      $name    Location display name.
	 * @param string|null $address Optional venue/city string.
	 * @return int|false Inserted row ID or false on failure.
	 */
	public static function insert( string $name, ?string $address = null ): int|false {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_pos_locations';
		$result = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- DAO layer -- direct DB access is intentional per MBS architecture
			$table,
			array(
				'name'      => sanitize_text_field( $name ),
				'address'   => $address ? sanitize_text_field( $address ) : null,
				'is_active' => 1,
			),
			array( '%s', '%s', '%d' )
		);
		return false !== $result ? (int) $wpdb->insert_id : false;
	}

	/**
	 * Update name, address, and/or active state for an existing location.
	 *
	 * @param int         $id        Location ID.
	 * @param string      $name      New display name.
	 * @param string|null $address   New address (null clears it).
	 * @param int         $is_active 1 = active, 0 = inactive.
	 * @return bool
	 */
	public static function update( int $id, string $name, ?string $address, int $is_active ): bool {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_pos_locations';
		return false !== $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
			$table,
			array(
				'name'      => sanitize_text_field( $name ),
				'address'   => $address ? sanitize_text_field( $address ) : null,
				'is_active' => (int) $is_active,
			),
			array( 'id' => $id ),
			array( '%s', '%s', '%d' ),
			array( '%d' )
		);
	}

	/**
	 * Toggle the is_active flag for a location.
	 *
	 * @param int $id Location ID.
	 * @return bool
	 */
	public static function toggle_active( int $id ): bool {
		global $wpdb;
		$table    = $wpdb->prefix . 'mbs_pos_locations';
		$location = self::get( $id );
		if ( ! $location ) {
			return false;
		}
		$new_state = $location->is_active ? 0 : 1;
		return false !== $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
			$table,
			array( 'is_active' => $new_state ),
			array( 'id' => $id ),
			array( '%d' ),
			array( '%d' )
		);
	}

	/**
	 * Delete a location row.  Only call after confirming has_inventory() is false.
	 *
	 * @param int $id Location ID.
	 * @return bool
	 */
	public static function delete( int $id ): bool {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_pos_locations';
		return false !== $wpdb->delete( $table, array( 'id' => $id ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
	}

	/**
	 * Return true if any mbs_pos_inventory rows reference this location.
	 * Used to guard against deleting locations that have stock records.
	 *
	 * @param int $id Location ID.
	 * @return bool
	 */
	public static function has_inventory( int $id ): bool {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_pos_inventory';
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE location_id = %d", $id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
		return $count > 0;
	}
}
