<?php
/**
 * Eliminación lógica reutilizable para tablas del plugin.
 *
 * @package Logistica
 */

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

/**
 * Marca registros como eliminados sin DELETE físico y permite restauración.
 */
class Logistica_Soft_Delete {

	/**
	 * @var array<string, bool>
	 */
	private static $column_cache = array();

	/**
	 * Registro de tablas con metadatos de trazabilidad.
	 *
	 * @return array<string, array{module_key: string, entity_type: string, label_field: string}>
	 */
	public static function registry() {
		return array(
			'inventario_bodega'      => array(
				'module_key'  => 'warehouse',
				'entity_type' => 'item',
				'label_field' => 'codigo',
			),
			'minutas'                => array(
				'module_key'  => 'minutas',
				'entity_type' => 'minuta',
				'label_field' => 'minuta',
			),
			'pedidos_inventario'     => array(
				'module_key'  => 'pedidos_board',
				'entity_type' => 'inventario',
				'label_field' => 'referencia',
			),
			'pedidos_ordenes_compra' => array(
				'module_key'  => 'pedidos_board',
				'entity_type' => 'orden',
				'label_field' => 'oc_numero',
			),
			'pedidos_ordenes_lineas' => array(
				'module_key'  => 'pedidos_board',
				'entity_type' => 'orden_linea',
				'label_field' => 'articulo',
			),
			'pedidos_entregas'       => array(
				'module_key'  => 'pedidos_board',
				'entity_type' => 'entrega',
				'label_field' => 'remision',
			),
			'pedidos_orden_linea_entregas' => array(
				'module_key'  => 'pedidos_board',
				'entity_type' => 'orden_linea_entrega',
				'label_field' => 'factura',
			),
			'etiqueta_productos'     => array(
				'module_key'  => 'etiqueta_catalog',
				'entity_type' => 'producto',
				'label_field' => 'referencia',
			),
			'rotulo_rollos'          => array(
				'module_key'  => 'rotulo_rollos',
				'entity_type' => 'rotulo',
				'label_field' => 'tipo',
			),
			'cierre_caracteristicas' => array(
				'module_key'  => 'cierre_ordenes',
				'entity_type' => 'caracteristica',
				'label_field' => 'codigo',
			),
			'planeacion_impresion'   => array(
				'module_key'  => 'planeacion_impresion',
				'entity_type' => 'plan',
				'label_field' => 'maquina',
			),
			'planeacion_formado'     => array(
				'module_key'  => 'planeacion_formado',
				'entity_type' => 'plan',
				'label_field' => 'maquina',
			),
		);
	}

	/**
	 * @param string $table_key Clave de tabla en Logistica_Database.
	 * @return array{module_key: string, entity_type: string, label_field: string}|null
	 */
	public static function meta_for_table( $table_key ) {
		$registry = self::registry();
		return $registry[ $table_key ] ?? null;
	}

	/**
	 * Agrega columnas is_deleted, deleted_at, deleted_by a tablas registradas.
	 *
	 * @return void
	 */
	public static function migrate_all_columns() {
		global $wpdb;

		foreach ( array_keys( self::registry() ) as $table_key ) {
			$table = Logistica_Database::table( $table_key );
			if ( ! $table ) {
				continue;
			}

			$exists = $wpdb->get_var(
				$wpdb->prepare( 'SHOW TABLES LIKE %s', $table )
			);
			if ( $exists !== $table ) {
				continue;
			}

			$column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'is_deleted'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			if ( empty( $column ) ) {
				$wpdb->query( "ALTER TABLE {$table} ADD is_deleted tinyint(1) NOT NULL DEFAULT 0 AFTER updated_at" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$wpdb->query( "ALTER TABLE {$table} ADD deleted_at datetime NULL AFTER is_deleted" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$wpdb->query( "ALTER TABLE {$table} ADD deleted_by bigint(20) unsigned NOT NULL DEFAULT 0 AFTER deleted_at" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$wpdb->query( "ALTER TABLE {$table} ADD KEY is_deleted (is_deleted)" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			}

			self::$column_cache[ $table_key ] = true;
		}
	}

	/**
	 * @param string $table_key Clave de tabla.
	 * @return bool
	 */
	public static function table_supports_soft_delete( $table_key ) {
		if ( isset( self::$column_cache[ $table_key ] ) ) {
			return self::$column_cache[ $table_key ];
		}

		global $wpdb;

		$table = Logistica_Database::table( $table_key );
		if ( ! $table ) {
			self::$column_cache[ $table_key ] = false;
			return false;
		}

		$column = $wpdb->get_results( "SHOW COLUMNS FROM {$table} LIKE 'is_deleted'", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$supported                        = ! empty( $column );
		self::$column_cache[ $table_key ] = $supported;

		return $supported;
	}

	/**
	 * Fragmento SQL para excluir eliminados.
	 *
	 * @param string $table_key Clave de tabla.
	 * @param string $alias     Alias SQL opcional.
	 * @return string
	 */
	public static function sql_not_deleted( $table_key, $alias = '' ) {
		if ( ! self::table_supports_soft_delete( $table_key ) ) {
			return '1=1';
		}

		$prefix = '' !== $alias ? rtrim( $alias, '.' ) . '.' : '';
		return '(' . $prefix . 'is_deleted = 0 OR ' . $prefix . 'is_deleted IS NULL)';
	}

	/**
	 * @param string $table_key Clave de tabla.
	 * @param int    $id        ID del registro.
	 * @return array<string, mixed>|null
	 */
	public static function get_row( $table_key, $id, $include_deleted = false ) {
		global $wpdb;

		$table = Logistica_Database::table( $table_key );
		$id    = (int) $id;
		if ( ! $table || $id <= 0 ) {
			return null;
		}

		$sql = "SELECT * FROM {$table} WHERE id = %d"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( ! $include_deleted && self::table_supports_soft_delete( $table_key ) ) {
			$sql .= ' AND ' . self::sql_not_deleted( $table_key );
		}
		$sql .= ' LIMIT 1';

		$row = $wpdb->get_row( $wpdb->prepare( $sql, $id ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		return is_array( $row ) ? $row : null;
	}

	/**
	 * @param array<string, mixed> $row Fila DB.
	 * @return bool
	 */
	public static function row_is_deleted( $row ) {
		return ! empty( $row['is_deleted'] );
	}

	/**
	 * @param string               $table_key Clave de tabla.
	 * @param int                  $id        ID.
	 * @param array<string, mixed> $row       Snapshot opcional.
	 * @return true|WP_Error
	 */
	public static function soft_delete( $table_key, $id, $row = null ) {
		if ( ! Logistica_Entity_Lifecycle::can_soft_delete() ) {
			return new WP_Error( 'forbidden', __( 'No tienes permiso para eliminar registros.', 'logistica' ) );
		}

		global $wpdb;

		$meta = self::meta_for_table( $table_key );
		if ( ! $meta ) {
			return new WP_Error( 'unsupported_table', __( 'Tabla no registrada para eliminación lógica.', 'logistica' ) );
		}

		$id  = (int) $id;
		$row = is_array( $row ) ? $row : self::get_row( $table_key, $id, true );
		if ( ! $row || self::row_is_deleted( $row ) ) {
			return new WP_Error( 'not_found', __( 'Registro no encontrado.', 'logistica' ) );
		}

		$label_field = (string) ( $meta['label_field'] ?? 'id' );
		$entity_label = trim( (string) ( $row[ $label_field ] ?? '' ) );
		if ( '' === $entity_label ) {
			$entity_label = '#' . $id;
		}

		Logistica_Entity_Lifecycle::record_delete(
			$meta['module_key'],
			$meta['entity_type'],
			(string) $id,
			$row,
			$entity_label
		);

		if ( ! self::table_supports_soft_delete( $table_key ) ) {
			return new WP_Error( 'missing_columns', __( 'La tabla aún no tiene columnas de eliminación lógica.', 'logistica' ) );
		}

		$table = Logistica_Database::table( $table_key );
		$updated = $wpdb->update(
			$table,
			array(
				'is_deleted' => 1,
				'deleted_at' => current_time( 'mysql', true ),
				'deleted_by' => get_current_user_id(),
				'updated_at' => current_time( 'mysql' ),
			),
			array( 'id' => $id ),
			array( '%d', '%s', '%d', '%s' ),
			array( '%d' )
		);

		if ( false === $updated ) {
			return new WP_Error( 'db_error', __( 'No se pudo marcar el registro como eliminado.', 'logistica' ) );
		}

		return true;
	}

	/**
	 * @param string $table_key Clave de tabla.
	 * @param int    $id        ID.
	 * @return true|WP_Error
	 */
	public static function restore( $table_key, $id ) {
		global $wpdb;

		$table = Logistica_Database::table( $table_key );
		$id    = (int) $id;
		if ( ! $table || $id <= 0 || ! self::table_supports_soft_delete( $table_key ) ) {
			return new WP_Error( 'invalid', __( 'No se puede restaurar este registro.', 'logistica' ) );
		}

		$row = self::get_row( $table_key, $id, true );
		if ( ! $row || ! self::row_is_deleted( $row ) ) {
			return new WP_Error( 'not_deleted', __( 'El registro no está eliminado.', 'logistica' ) );
		}

		$updated = $wpdb->update(
			$table,
			array(
				'is_deleted' => 0,
				'deleted_at' => null,
				'deleted_by' => 0,
				'updated_at' => current_time( 'mysql' ),
			),
			array( 'id' => $id ),
			array( '%d', '%s', '%d', '%s' ),
			array( '%d' )
		);

		if ( false === $updated ) {
			return new WP_Error( 'db_error', __( 'No se pudo restaurar el registro.', 'logistica' ) );
		}

		return true;
	}

	/**
	 * @param string               $table_key Clave de tabla.
	 * @param int                  $id        ID.
	 * @param array<string, mixed> $payload   Snapshot para reinsertar si fue purgado.
	 * @return true|WP_Error
	 */
	public static function restore_from_payload( $table_key, $id, $payload = array() ) {
		$existing = self::get_row( $table_key, (int) $id, true );
		if ( $existing ) {
			if ( self::row_is_deleted( $existing ) ) {
				return self::restore( $table_key, (int) $id );
			}
			return true;
		}

		if ( ! is_array( $payload ) || empty( $payload ) ) {
			return new WP_Error( 'missing_payload', __( 'No hay datos para restaurar el registro.', 'logistica' ) );
		}

		global $wpdb;

		$table = Logistica_Database::table( $table_key );
		if ( ! $table ) {
			return new WP_Error( 'invalid_table', __( 'Tabla no válida.', 'logistica' ) );
		}

		unset( $payload['is_deleted'], $payload['deleted_at'], $payload['deleted_by'] );
		$payload['id']         = (int) $id;
		$payload['updated_at'] = current_time( 'mysql' );
		if ( empty( $payload['created_at'] ) ) {
			$payload['created_at'] = current_time( 'mysql' );
		}

		$formats = array_fill( 0, count( $payload ), '%s' );
		if ( isset( $payload['id'] ) ) {
			$formats[0] = '%d';
		}

		$replaced = $wpdb->replace( $table, $payload );
		if ( ! $replaced ) {
			return new WP_Error( 'db_error', __( 'No se pudo reinsertar el registro restaurado.', 'logistica' ) );
		}

		return true;
	}

	/**
	 * Eliminación física (solo administradores).
	 *
	 * @param string $table_key Clave de tabla.
	 * @param int    $id        ID.
	 * @return true|WP_Error
	 */
	public static function hard_delete( $table_key, $id ) {
		if ( ! Logistica_Entity_Lifecycle::can_purge() ) {
			return new WP_Error( 'forbidden', __( 'No tienes permiso para eliminar definitivamente.', 'logistica' ) );
		}

		global $wpdb;

		$table = Logistica_Database::table( $table_key );
		$id    = (int) $id;
		if ( ! $table || $id <= 0 ) {
			return new WP_Error( 'invalid', __( 'Registro no válido.', 'logistica' ) );
		}

		$row  = self::get_row( $table_key, $id, true );
		$meta = self::meta_for_table( $table_key );

		if ( $meta && $row ) {
			Logistica_Entity_Lifecycle::record_purge(
				$meta['module_key'],
				$meta['entity_type'],
				(string) $id,
				$row
			);
		}

		$deleted = $wpdb->delete( $table, array( 'id' => $id ), array( '%d' ) );
		if ( ! $deleted ) {
			return new WP_Error( 'db_error', __( 'No se pudo eliminar el registro definitivamente.', 'logistica' ) );
		}

		return true;
	}

	/**
	 * Marca múltiples filas como eliminadas sin eventos individuales de papelera.
	 *
	 * @param string       $table_key Clave de tabla.
	 * @param array<int>   $ids       IDs a marcar.
	 * @return int Filas afectadas.
	 */
	public static function mark_deleted_batch( $table_key, $ids ) {
		global $wpdb;

		if ( ! self::table_supports_soft_delete( $table_key ) ) {
			return 0;
		}

		$ids = array_values(
			array_filter(
				array_map( 'intval', (array) $ids ),
				static function ( $id ) {
					return $id > 0;
				}
			)
		);

		if ( empty( $ids ) ) {
			return 0;
		}

		$table       = Logistica_Database::table( $table_key );
		$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
		$now         = current_time( 'mysql', true );
		$user_id     = get_current_user_id();

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPlaceholder
		$sql = "UPDATE {$table} SET is_deleted = 1, deleted_at = %s, deleted_by = %d, updated_at = %s WHERE id IN ({$placeholders}) AND " . self::sql_not_deleted( $table_key );

		$params = array_merge( array( $now, $user_id, current_time( 'mysql' ) ), $ids );
		return (int) $wpdb->query( $wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
	}

	/**
	 * @param string $module_key  Módulo.
	 * @param string $entity_type Tipo.
	 * @return string|null
	 */
	public static function table_key_for_entity( $module_key, $entity_type ) {
		foreach ( self::registry() as $table_key => $meta ) {
			if ( $meta['module_key'] === $module_key && $meta['entity_type'] === $entity_type ) {
				return $table_key;
			}
		}
		return null;
	}
}
