<?php
/**
 * Catálogo manual de códigos de producto para etiquetas (6 dígitos).
 *
 * @package Logistica
 */

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

/**
 * CRUD de referencias → codigo_producto.
 */
class Logistica_Label_Product_Catalog {

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

		$table = Logistica_Database::table( 'etiqueta_productos' );
		if ( ! $table ) {
			return array();
		}

		$rows = $wpdb->get_results(
			'SELECT * FROM ' . $table . ' WHERE ' . Logistica_Soft_Delete::sql_not_deleted( 'etiqueta_productos' ) . ' ORDER BY referencia ASC, mercado ASC, tipo_producto ASC', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			ARRAY_A
		);

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

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

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

		return array(
			'id'              => (int) ( $row['id'] ?? 0 ),
			'referencia'      => (string) ( $row['referencia'] ?? '' ),
			'tipo_producto'   => Logistica_Label_Barcode::normalize_product_type( $row['tipo_producto'] ?? '' ),
			'mercado'         => strtoupper( (string) ( $row['mercado'] ?? 'NAC' ) ),
			'tamano'          => (string) ( $row['tamano'] ?? '' ),
			'codigo_producto' => Logistica_Label_Barcode::normalize_product_code( $row['codigo_producto'] ?? '' ),
			'unidad_empaque'  => sanitize_text_field( (string) ( $row['unidad_empaque'] ?? '' ) ),
			'observaciones'   => (string) ( $row['observaciones'] ?? '' ),
			'updated_at'      => (string) ( $row['updated_at'] ?? '' ),
		);
	}

	/**
	 * Etiqueta visible de producto: tipo + tamaño.
	 *
	 * @param string $tipo_label Nombre del tipo (ej. Vaso).
	 * @param string $tamano     Tamaño (ej. 8 OZ).
	 * @return string
	 */
	public static function build_producto_label( $tipo_label, $tamano ) {
		$tipo_label = trim( (string) $tipo_label );
		$tamano     = trim( (string) $tamano );

		if ( '' !== $tipo_label && '' !== $tamano ) {
			return $tipo_label . ' ' . $tamano;
		}

		return $tipo_label ?: $tamano;
	}

	/**
	 * @param array<string, mixed> $row Fila hidratada.
	 * @return array<string, mixed>|null
	 */
	public static function format_row_for_client( $row ) {
		if ( ! is_array( $row ) || empty( $row['id'] ) ) {
			return null;
		}

		$type_options = Logistica_Label_Barcode::product_type_options();
		$tipo_label   = (string) ( $type_options[ $row['tipo_producto'] ] ?? $row['tipo_producto'] );
		$ean          = Logistica_Label_Barcode::build_ean13(
			$row['tipo_producto'],
			$row['mercado'],
			$row['codigo_producto']
		);

		$hidden_issues = Logistica_Label_Etiqueta_Option_Usage::row_hidden_issues( $row );

		return array(
			'id'                  => (int) $row['id'],
			'referencia'          => (string) $row['referencia'],
			'tipo_producto'       => (string) $row['tipo_producto'],
			'tipo_label'          => $tipo_label,
			'mercado'             => (string) $row['mercado'],
			'tamano'              => (string) $row['tamano'],
			'codigo_producto'     => (string) $row['codigo_producto'],
			'unidad_empaque'      => (string) $row['unidad_empaque'],
			'observaciones'       => (string) $row['observaciones'],
			'ean13'               => is_wp_error( $ean ) ? '' : (string) $ean,
			'producto_label'      => self::build_producto_label( $tipo_label, (string) $row['tamano'] ),
			'hidden_issues'       => $hidden_issues,
			'has_hidden_options'  => ! empty( $hidden_issues ),
		);
	}

	/**
	 * @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;
		}

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

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

	/**
	 * Busca código de producto por referencia + tipo + mercado.
	 *
	 * @param string $referencia    Nombre referencia.
	 * @param string $tipo_producto Tipo etiqueta.
	 * @param string $mercado       EXP|NAC.
	 * @return array<string, mixed>|null
	 */
	public static function find_match( $referencia, $tipo_producto, $mercado ) {
		global $wpdb;

		$referencia    = sanitize_text_field( (string) $referencia );
		$tipo_producto = Logistica_Label_Barcode::normalize_product_type( $tipo_producto );
		$mercado       = strtoupper( trim( (string) $mercado ) );

		if ( '' === $referencia ) {
			return null;
		}

		$table = Logistica_Database::table( 'etiqueta_productos' );
		if ( ! $table ) {
			return null;
		}

		if ( '' !== $tipo_producto && Logistica_Label_Etiqueta_Mercados::is_valid_key( $mercado ) ) {
			$row = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT * FROM {$table} WHERE referencia = %s AND tipo_producto = %s AND mercado = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
					$referencia,
					$tipo_producto,
					$mercado
				),
				ARRAY_A
			);

			if ( is_array( $row ) ) {
				return self::hydrate_row( $row );
			}
		}

		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$table} WHERE referencia = %s ORDER BY updated_at DESC, id DESC LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$referencia
			),
			ARRAY_A
		);

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

	/**
	 * Busca otra fila activa con el mismo código de producto (6 dígitos).
	 *
	 * @param string $codigo_producto Código 6 dígitos.
	 * @param int    $exclude_id      ID a excluir (edición).
	 * @return array<string, mixed>|null
	 */
	public static function find_other_by_codigo_producto( $codigo_producto, $exclude_id = 0 ) {
		global $wpdb;

		$codigo_producto = Logistica_Label_Barcode::normalize_product_code( $codigo_producto );
		$exclude_id      = (int) $exclude_id;

		if ( 6 !== strlen( $codigo_producto ) ) {
			return null;
		}

		$table       = Logistica_Database::table( 'etiqueta_productos' );
		$not_deleted = Logistica_Soft_Delete::sql_not_deleted( 'etiqueta_productos' );
		if ( ! $table ) {
			return null;
		}

		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$table} WHERE {$not_deleted} AND codigo_producto = %s AND id <> %d ORDER BY id ASC LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$codigo_producto,
				$exclude_id
			),
			ARRAY_A
		);

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

	/**
	 * Resumen de códigos / EAN-13 duplicados en el catálogo activo.
	 *
	 * @param array<int, array<string, mixed>>|null $rows Filas hidratadas o null para consultar.
	 * @return array{
	 *   has_duplicates:bool,
	 *   codigo_groups:int,
	 *   ean_groups:int,
	 *   row_ids:list<int>,
	 *   codigos:list<string>,
	 *   eans:list<string>
	 * }
	 */
	public static function get_duplicate_report( $rows = null ) {
		if ( null === $rows ) {
			$rows = self::get_all();
		}

		$by_codigo = array();
		$by_ean    = array();

		foreach ( (array) $rows as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$id     = (int) ( $row['id'] ?? 0 );
			$codigo = Logistica_Label_Barcode::normalize_product_code( $row['codigo_producto'] ?? '' );
			if ( $id <= 0 || 6 !== strlen( $codigo ) ) {
				continue;
			}

			if ( ! isset( $by_codigo[ $codigo ] ) ) {
				$by_codigo[ $codigo ] = array();
			}
			$by_codigo[ $codigo ][] = $id;

			$ean = Logistica_Label_Barcode::build_ean13(
				$row['tipo_producto'] ?? '',
				$row['mercado'] ?? '',
				$codigo
			);
			if ( is_wp_error( $ean ) || '' === (string) $ean ) {
				continue;
			}

			$ean = (string) $ean;
			if ( ! isset( $by_ean[ $ean ] ) ) {
				$by_ean[ $ean ] = array();
			}
			$by_ean[ $ean ][] = $id;
		}

		$codigo_dupes = array();
		$ean_dupes    = array();
		$row_ids      = array();

		foreach ( $by_codigo as $codigo => $ids ) {
			if ( count( $ids ) < 2 ) {
				continue;
			}
			$codigo_dupes[] = $codigo;
			foreach ( $ids as $id ) {
				$row_ids[ (int) $id ] = true;
			}
		}

		foreach ( $by_ean as $ean => $ids ) {
			if ( count( $ids ) < 2 ) {
				continue;
			}
			$ean_dupes[] = $ean;
			foreach ( $ids as $id ) {
				$row_ids[ (int) $id ] = true;
			}
		}

		sort( $codigo_dupes, SORT_STRING );
		sort( $ean_dupes, SORT_STRING );

		return array(
			'has_duplicates' => ! empty( $row_ids ),
			'codigo_groups'  => count( $codigo_dupes ),
			'ean_groups'     => count( $ean_dupes ),
			'row_ids'        => array_map( 'intval', array_keys( $row_ids ) ),
			'codigos'        => $codigo_dupes,
			'eans'           => $ean_dupes,
		);
	}

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

		$table = Logistica_Database::table( 'etiqueta_productos' );
		if ( ! $table ) {
			return new WP_Error( 'missing_table', __( 'Tabla de códigos de etiqueta no disponible.', 'logistica' ) );
		}

		$id              = absint( $data['id'] ?? 0 );
		$referencia      = strtoupper( sanitize_text_field( (string) ( $data['referencia'] ?? '' ) ) );
		$tipo_producto   = Logistica_Label_Barcode::normalize_product_type( $data['tipo_producto'] ?? '' );
		$mercado         = strtoupper( sanitize_text_field( (string) ( $data['mercado'] ?? 'NAC' ) ) );
		$tamano          = sanitize_text_field( (string) ( $data['tamano'] ?? '' ) );
		if ( '__new__' === $tamano ) {
			$tamano = sanitize_text_field( (string) ( $data['tamano_new'] ?? '' ) );
		}
		$codigo_producto = Logistica_Label_Barcode::normalize_product_code( $data['codigo_producto'] ?? '' );
		$observaciones   = sanitize_textarea_field( (string) ( $data['observaciones'] ?? '' ) );
		$now             = current_time( 'mysql' );

		if ( '' === $referencia ) {
			return new WP_Error( 'missing_reference', __( 'La referencia es obligatoria.', 'logistica' ) );
		}

		if ( '' === $tipo_producto ) {
			return new WP_Error( 'missing_type', __( 'Selecciona un tipo de producto válido.', 'logistica' ) );
		}

		if ( ! Logistica_Label_Etiqueta_Mercados::is_valid_key( $mercado ) ) {
			return new WP_Error( 'missing_market', __( 'Mercado inválido. Selecciona un mercado del catálogo.', 'logistica' ) );
		}

		$tamano = Logistica_Label_Etiqueta_Tamanos::normalize_label( $tamano );
		if ( '' === $tamano || '__new__' === $tamano ) {
			return new WP_Error( 'missing_size', __( 'Selecciona o ingresa un tamaño.', 'logistica' ) );
		}

		$tamano = Logistica_Label_Etiqueta_Tamanos::remember( $tamano );

		if ( 6 !== strlen( $codigo_producto ) ) {
			return new WP_Error( 'invalid_code', __( 'El código de producto debe tener 6 dígitos numéricos.', 'logistica' ) );
		}

		$existing_row = $id > 0 ? self::get_by_id( $id ) : null;
		$previous_codigo = $existing_row
			? Logistica_Label_Barcode::normalize_product_code( $existing_row['codigo_producto'] ?? '' )
			: '';
		$previous_ean = '';
		if ( $existing_row ) {
			$prev_ean_build = Logistica_Label_Barcode::build_ean13(
				$existing_row['tipo_producto'] ?? '',
				$existing_row['mercado'] ?? '',
				$previous_codigo
			);
			if ( ! is_wp_error( $prev_ean_build ) ) {
				$previous_ean = (string) $prev_ean_build;
			}
		}

		$duplicate_code = self::find_other_by_codigo_producto( $codigo_producto, $id );
		if ( $duplicate_code && $previous_codigo !== $codigo_producto ) {
			return new WP_Error(
				'duplicate_codigo',
				sprintf(
					/* translators: 1: product code, 2: reference name */
					__( 'El código de producto «%1$s» ya está asignado a la referencia «%2$s». Usa un código distinto para evitar códigos de barras duplicados.', 'logistica' ),
					$codigo_producto,
					(string) ( $duplicate_code['referencia'] ?? '' )
				)
			);
		}

		$ean = Logistica_Label_Barcode::build_ean13( $tipo_producto, $mercado, $codigo_producto );
		if ( ! is_wp_error( $ean ) && '' !== (string) $ean && $previous_ean !== (string) $ean ) {
			foreach ( self::get_all() as $existing ) {
				if ( (int) ( $existing['id'] ?? 0 ) === $id ) {
					continue;
				}
				$existing_ean = Logistica_Label_Barcode::build_ean13(
					$existing['tipo_producto'] ?? '',
					$existing['mercado'] ?? '',
					$existing['codigo_producto'] ?? ''
				);
				if ( ! is_wp_error( $existing_ean ) && (string) $existing_ean === (string) $ean ) {
					return new WP_Error(
						'duplicate_ean',
						sprintf(
							/* translators: 1: EAN-13, 2: reference name */
							__( 'El EAN-13 «%1$s» ya existe en la referencia «%2$s». Corrige tipo, mercado o código de producto.', 'logistica' ),
							(string) $ean,
							(string) ( $existing['referencia'] ?? '' )
						)
					);
				}
			}
		}

		$payload = array(
			'referencia'      => $referencia,
			'tipo_producto'   => $tipo_producto,
			'mercado'         => $mercado,
			'tamano'          => $tamano,
			'codigo_producto' => $codigo_producto,
			'unidad_empaque'  => '',
			'observaciones'   => $observaciones,
			'updated_at'      => $now,
		);

		if ( $id > 0 ) {
			$existing_row = self::get_by_id( $id );
			if ( $existing_row ) {
				$updated = $wpdb->update(
					$table,
					$payload,
					array( 'id' => $id ),
					array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ),
					array( '%d' )
				);

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

				$saved = self::get_by_id( $id );
				if ( ! $saved ) {
					return new WP_Error( 'db_error', __( 'No se pudo recuperar el registro actualizado.', 'logistica' ) );
				}

				return $saved;
			}

			$id = 0;
		}

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

		if ( ! $inserted ) {
			return new WP_Error( 'db_error', __( 'No se pudo crear el registro.', 'logistica' ) );
		}

		return self::get_by_id( (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 válido.', 'logistica' ) );
		}

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

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

	/**
	 * @param int $catalog_id ID catálogo etiqueta.
	 * @return array{row:array<string,mixed>,barcode:string,preview:array<string,mixed>}|\WP_Error
	 */
	public static function resolve_barcode_for_catalog_id( $catalog_id ) {
		$row = self::get_by_id( (int) $catalog_id );

		if ( ! $row ) {
			return new WP_Error( 'catalog_miss', __( 'Registro de etiqueta no encontrado.', 'logistica' ) );
		}

		return self::resolve_barcode_from_row( $row );
	}

	/**
	 * @param array<string, mixed> $row Fila catálogo.
	 * @return array{row:array<string,mixed>,barcode:string,preview:array<string,mixed>}|\WP_Error
	 */
	public static function resolve_barcode_from_row( $row ) {
		if ( ! is_array( $row ) || empty( $row['codigo_producto'] ) ) {
			return new WP_Error( 'catalog_miss', __( 'El registro no tiene código de producto válido.', 'logistica' ) );
		}

		$tipo_use    = (string) ( $row['tipo_producto'] ?? '' );
		$mercado_use = strtoupper( (string) ( $row['mercado'] ?? 'NAC' ) );
		$barcode     = Logistica_Label_Barcode::build_ean13( $tipo_use, $mercado_use, $row['codigo_producto'] );

		if ( is_wp_error( $barcode ) ) {
			return $barcode;
		}

		return array(
			'row'     => $row,
			'barcode' => $barcode,
			'preview' => array(
				'pais'            => Logistica_Label_Barcode::COUNTRY_PREFIX,
				'mercado'         => $mercado_use,
				'mercado_digit'   => Logistica_Label_Barcode::market_digit( $mercado_use ),
				'tipo'            => $tipo_use,
				'tipo_code'       => Logistica_Label_Barcode::type_code( $tipo_use ),
				'codigo_producto' => $row['codigo_producto'],
			),
		);
	}

	/**
	 * @param string $referencia Referencia cierre.
	 * @param string $tipo_producto Tipo cierre.
	 * @param string $variante Variante cierre.
	 * @return array{row:array<string,mixed>,barcode:string,preview:array<string,mixed>}|\WP_Error
	 */
	public static function resolve_barcode_for_reference( $referencia, $tipo_producto, $variante ) {
		$tipo    = Logistica_Label_Barcode::closing_type_to_label_type( $tipo_producto );
		$mercado = Logistica_Label_Barcode::normalize_market( $variante );
		$row     = self::find_match( $referencia, $tipo, $mercado );

		if ( ! $row ) {
			return new WP_Error(
				'catalog_miss',
				sprintf(
					/* translators: %s: product reference */
					__( 'No hay código de producto registrado para la referencia «%s». Agrégalo en Códigos etiqueta.', 'logistica' ),
					$referencia
				)
			);
		}

		if ( '' !== $tipo && empty( $row['tipo_producto'] ) ) {
			$row['tipo_producto'] = $tipo;
		}
		if ( '' !== $mercado && empty( $row['mercado'] ) ) {
			$row['mercado'] = $mercado;
		}

		return self::resolve_barcode_from_row( $row );
	}
}
