<?php
/**
 * Maestro de caracteristicas por codigo para bases de cierre
 * (cajas: largo/ancho/alto/clave; bolsas: largo/ancho/calibre).
 *
 * @package Logistica
 */

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

/**
 * CRUD y overlay de dimensiones en bases de cierre.
 */
class Logistica_Order_Closing_Characteristics {

	/**
	 * Tipos de base que usan dimensiones bloqueadas desde este maestro.
	 *
	 * @return string[]
	 */
	public static function locked_base_types() {
		return array( 'cajas', 'cajas_saldo', 'bolsas', 'acetato' );
	}

	/**
	 * Campos de dimension posibles en el maestro.
	 *
	 * @return string[]
	 */
	public static function dimension_fields() {
		return array( 'largo', 'ancho', 'alto', 'clave', 'calibre' );
	}

	/**
	 * Campos del maestro aplicables a un tipo de base.
	 *
	 * @param string $tipo Tipo de base.
	 * @return string[]
	 */
	public static function fields_for_tipo( $tipo ) {
		$tipo = sanitize_key( (string) $tipo );

		if ( 'bolsas' === $tipo ) {
			return array( 'largo', 'ancho', 'calibre' );
		}

		if ( in_array( $tipo, array( 'cajas', 'cajas_saldo' ), true ) ) {
			return array( 'largo', 'ancho', 'alto', 'clave' );
		}

		if ( 'acetato' === $tipo ) {
			return array( 'largo', 'ancho', 'alto', 'clave' );
		}

		if ( in_array( $tipo, self::locked_base_types(), true ) ) {
			return array( 'largo', 'ancho', 'alto', 'clave' );
		}

		return array();
	}

	/**
	 * Etiquetas de campos segun tipo (unidades).
	 *
	 * @param string $tipo Tipo de base.
	 * @return array<string, string>
	 */
	public static function field_labels_for_tipo( $tipo ) {
		$tipo = sanitize_key( (string) $tipo );

		if ( 'bolsas' === $tipo ) {
			return array(
				'largo'   => __( 'Largo (pulg.)', 'logistica' ),
				'ancho'   => __( 'Ancho (pulg.)', 'logistica' ),
				'calibre' => __( 'Calibre (mm)', 'logistica' ),
			);
		}

		if ( 'acetato' === $tipo ) {
			return array(
				'largo' => __( 'Largo lámina (cm)', 'logistica' ),
				'ancho' => __( 'Ancho lámina (cm)', 'logistica' ),
				'alto'  => __( 'Alto (cm)', 'logistica' ),
				'clave' => __( 'Clave', 'logistica' ),
			);
		}

		return array(
			'largo' => __( 'Largo (mm)', 'logistica' ),
			'ancho' => __( 'Ancho (mm)', 'logistica' ),
			'alto'  => __( 'Alto (mm)', 'logistica' ),
			'clave' => __( 'Clave', 'logistica' ),
		);
	}

	/**
	 * ¿La fila tiene todos los campos requeridos para su tipo?
	 *
	 * @param array<string, mixed> $row Fila.
	 * @return bool
	 */
	public static function row_is_complete( $row ) {
		if ( ! is_array( $row ) ) {
			return false;
		}

		$tipo   = sanitize_key( $row['tipo'] ?? 'cajas' );
		$fields = self::fields_for_tipo( $tipo );
		if ( empty( $fields ) ) {
			return false;
		}

		foreach ( $fields as $field ) {
			if ( '' === trim( (string) ( $row[ $field ] ?? '' ) ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * @return array<string, string>
	 */
	public static function tipo_options() {
		return Logistica_Order_Closing::base_type_options();
	}

	/**
	 * @return array<int, array<string, mixed>>
	 */
	public static function get_all() {
		global $wpdb;

		Logistica_Database::ensure_cierre_caracteristicas_table();

		$table       = Logistica_Database::table( 'cierre_caracteristicas' );
		$not_deleted = Logistica_Soft_Delete::sql_not_deleted( 'cierre_caracteristicas' );
		$rows        = $wpdb->get_results( "SELECT * FROM {$table} WHERE {$not_deleted} ORDER BY codigo ASC", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		if ( ! is_array( $rows ) ) {
			return array();
		}

		return array_map( array( __CLASS__, 'hydrate_row' ), $rows );
	}

	/**
	 * @param string $codigo Codigo insumo.
	 * @return array<string, mixed>|null
	 */
	public static function get_by_codigo( $codigo ) {
		global $wpdb;

		$codigo = self::normalize_codigo( $codigo );
		if ( '' === $codigo ) {
			return null;
		}

		Logistica_Database::ensure_cierre_caracteristicas_table();

		$table       = Logistica_Database::table( 'cierre_caracteristicas' );
		$not_deleted = Logistica_Soft_Delete::sql_not_deleted( 'cierre_caracteristicas' );
		$row         = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$table} WHERE codigo = %s AND {$not_deleted} LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$codigo
			),
			ARRAY_A
		);

		return is_array( $row ) ? self::hydrate_row( $row ) : null;
	}

	/**
	 * @param int $id Row ID.
	 * @return array<string, mixed>|null
	 */
	public static function get_by_id( $id ) {
		global $wpdb;

		$id = (int) $id;
		if ( $id <= 0 ) {
			return null;
		}

		Logistica_Database::ensure_cierre_caracteristicas_table();

		$table       = Logistica_Database::table( 'cierre_caracteristicas' );
		$not_deleted = Logistica_Soft_Delete::sql_not_deleted( 'cierre_caracteristicas' );
		$row         = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$table} WHERE id = %d AND {$not_deleted} LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$id
			),
			ARRAY_A
		);

		return is_array( $row ) ? self::hydrate_row( $row ) : null;
	}

	/**
	 * @param array<string, mixed> $row DB row.
	 * @return array<string, mixed>
	 */
	public static function format_row_for_client( $row ) {
		if ( ! is_array( $row ) ) {
			return array();
		}

		$tipo         = sanitize_key( $row['tipo'] ?? 'cajas' );
		$tipo_options = self::tipo_options();
		$tipo_label   = $tipo_options[ $tipo ] ?? $tipo;

		return array(
			'id'          => (int) ( $row['id'] ?? 0 ),
			'codigo'      => (string) ( $row['codigo'] ?? '' ),
			'descripcion' => (string) ( $row['descripcion'] ?? '' ),
			'tipo'        => $tipo,
			'tipo_label'  => (string) $tipo_label,
			'largo'       => (string) ( $row['largo'] ?? '' ),
			'ancho'       => (string) ( $row['ancho'] ?? '' ),
			'alto'        => (string) ( $row['alto'] ?? '' ),
			'clave'       => (string) ( $row['clave'] ?? '' ),
			'calibre'     => (string) ( $row['calibre'] ?? '' ),
			'fields'      => self::fields_for_tipo( $tipo ),
			'complete'    => self::row_is_complete( $row ),
			'search_text' => strtolower(
				implode(
					' ',
					array(
						(string) ( $row['codigo'] ?? '' ),
						(string) ( $row['descripcion'] ?? '' ),
						(string) $tipo_label,
						(string) ( $row['clave'] ?? '' ),
						(string) ( $row['calibre'] ?? '' ),
					)
				)
			),
		);
	}

	/**
	 * @return array<string, array<string, string>>
	 */
	public static function codigo_map() {
		$map = array();

		foreach ( self::get_all() as $row ) {
			$codigo = self::normalize_codigo( $row['codigo'] ?? '' );
			if ( '' === $codigo ) {
				continue;
			}

			$map[ $codigo ] = array(
				'largo'   => (string) ( $row['largo'] ?? '' ),
				'ancho'   => (string) ( $row['ancho'] ?? '' ),
				'alto'    => (string) ( $row['alto'] ?? '' ),
				'clave'   => (string) ( $row['clave'] ?? '' ),
				'calibre' => (string) ( $row['calibre'] ?? '' ),
				'tipo'    => sanitize_key( $row['tipo'] ?? 'cajas' ),
			);
		}

		return $map;
	}

	/**
	 * @param array<string, mixed> $config Config cierre.
	 * @return array<string, mixed>
	 */
	public static function apply_to_config_bases( $config ) {
		if ( empty( $config['bases'] ) || ! is_array( $config['bases'] ) ) {
			return $config;
		}

		$map = self::codigo_map();

		foreach ( $config['bases'] as $index => $base ) {
			if ( ! is_array( $base ) ) {
				continue;
			}

			$config['bases'][ $index ] = self::apply_to_base_row( $base, $map );
		}

		return $config;
	}

	/**
	 * @param array<string, mixed>                     $base Base row.
	 * @param array<string, array<string, string>>|null $map  Mapa codigo => dims.
	 * @return array<string, mixed>
	 */
	public static function apply_to_base_row( $base, $map = null ) {
		$tipo = sanitize_key( $base['tipo'] ?? '' );

		if ( ! in_array( $tipo, self::locked_base_types(), true ) ) {
			return $base;
		}

		$codigo = self::normalize_codigo( $base['cod'] ?? '' );
		if ( '' === $codigo ) {
			return $base;
		}

		if ( null === $map ) {
			$map = self::codigo_map();
		}

		if ( empty( $map[ $codigo ] ) ) {
			return $base;
		}

		$row    = $map[ $codigo ];
		$fields = self::fields_for_tipo( $tipo );

		foreach ( $fields as $field ) {
			if ( array_key_exists( $field, $row ) && '' !== trim( (string) $row[ $field ] ) ) {
				$base[ $field ] = (string) $row[ $field ];
			}
		}

		return $base;
	}

	/**
	 * @param array<string, mixed> $data Form data.
	 * @return int|\WP_Error
	 */
	public static function save_row( $data ) {
		global $wpdb;

		Logistica_Database::ensure_cierre_caracteristicas_table();

		$codigo = self::normalize_codigo( $data['codigo'] ?? '' );
		if ( '' === $codigo ) {
			return new WP_Error( 'missing_codigo', __( 'El codigo es obligatorio.', 'logistica' ) );
		}

		$tipo = sanitize_key( $data['tipo'] ?? 'cajas' );
		if ( ! array_key_exists( $tipo, self::tipo_options() ) ) {
			$tipo = 'cajas';
		}

		$payload = array(
			'codigo'      => $codigo,
			'descripcion' => sanitize_text_field( wp_unslash( $data['descripcion'] ?? '' ) ),
			'tipo'        => $tipo,
			'largo'       => self::sanitize_dimension( $data['largo'] ?? '' ),
			'ancho'       => self::sanitize_dimension( $data['ancho'] ?? '' ),
			'alto'        => self::sanitize_dimension( $data['alto'] ?? '' ),
			'clave'       => sanitize_text_field( wp_unslash( $data['clave'] ?? '' ) ),
			'calibre'     => self::sanitize_dimension( $data['calibre'] ?? '' ),
			'updated_at'  => current_time( 'mysql' ),
		);

		$allowed = self::fields_for_tipo( $tipo );
		foreach ( self::dimension_fields() as $field ) {
			if ( ! in_array( $field, $allowed, true ) ) {
				$payload[ $field ] = '';
			}
		}

		$table    = Logistica_Database::table( 'cierre_caracteristicas' );
		$existing = self::get_by_codigo( $codigo );

		if ( $existing ) {
			$updated = $wpdb->update(
				$table,
				$payload,
				array( 'id' => (int) ( $existing['id'] ?? 0 ) ),
				array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ),
				array( '%d' )
			);

			if ( false === $updated ) {
				return new WP_Error( 'db_error', __( 'No se pudo actualizar la caracteristica.', 'logistica' ) );
			}

			return (int) ( $existing['id'] ?? 0 );
		}

		$payload['created_at'] = current_time( 'mysql' );
		$formats               = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' );
		$inserted              = $wpdb->insert( $table, $payload, $formats );

		if ( false === $inserted ) {
			$db_error = (string) $wpdb->last_error;

			if ( '' !== $db_error && false !== stripos( $db_error, 'duplicate' ) ) {
				$duplicate_row = self::get_by_codigo( $codigo );
				if ( $duplicate_row ) {
					$update_payload = $payload;
					unset( $update_payload['created_at'] );
					$updated = $wpdb->update(
						$table,
						$update_payload,
						array( 'id' => (int) ( $duplicate_row['id'] ?? 0 ) ),
						array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ),
						array( '%d' )
					);

					if ( false !== $updated ) {
						return (int) ( $duplicate_row['id'] ?? 0 );
					}
				}

				return new WP_Error( 'duplicate_codigo', __( 'Este codigo ya esta registrado.', 'logistica' ) );
			}

			if ( '' !== $db_error && false !== stripos( $db_error, "doesn't exist" ) ) {
				Logistica_Database::create_tables();
				$inserted = $wpdb->insert( $table, $payload, $formats );
				if ( $inserted ) {
					return (int) $wpdb->insert_id;
				}
				$db_error = (string) $wpdb->last_error;
			}

			$message = __( 'No se pudo crear la caracteristica.', 'logistica' );
			if ( '' !== $db_error ) {
				$message .= ' ' . $db_error;
			}

			return new WP_Error( 'db_error', $message );
		}

		return (int) $wpdb->insert_id;
	}

	/**
	 * @param int $id Row ID.
	 * @return true|\WP_Error
	 */
	public static function delete_row( $id ) {
		$id = (int) $id;
		if ( $id <= 0 ) {
			return new WP_Error( 'invalid_id', __( 'Registro no valido.', 'logistica' ) );
		}

		$row = Logistica_Soft_Delete::get_row( 'cierre_caracteristicas', $id, true );
		if ( ! $row || Logistica_Soft_Delete::row_is_deleted( $row ) ) {
			return new WP_Error( 'not_found', __( 'Caracteristica no encontrada.', 'logistica' ) );
		}

		return Logistica_Soft_Delete::soft_delete( 'cierre_caracteristicas', $id, $row );
	}

	/**
	 * @param array<string, mixed> $row DB row.
	 * @return array<string, mixed>
	 */
	private static function hydrate_row( $row ) {
		if ( ! is_array( $row ) ) {
			return array();
		}

		if ( ! array_key_exists( 'calibre', $row ) ) {
			$row['calibre'] = '';
		}

		return $row;
	}

	/**
	 * @param string $codigo Codigo.
	 * @return string
	 */
	private static function normalize_codigo( $codigo ) {
		return strtoupper( trim( sanitize_text_field( (string) $codigo ) ) );
	}

	/**
	 * @param mixed $value Dimension.
	 * @return string
	 */
	private static function sanitize_dimension( $value ) {
		$value = trim( sanitize_text_field( wp_unslash( (string) $value ) ) );
		if ( '' === $value ) {
			return '';
		}

		return is_numeric( $value ) ? (string) (float) $value : $value;
	}
}
