<?php
/**
 * Tipos de producto personalizados para códigos de etiqueta.
 *
 * @package Logistica
 */

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

/**
 * Opciones de tipo persistidas (wp_options) + catálogo base + registros.
 */
class Logistica_Label_Etiqueta_Tipos {

	const OPTION_KEY        = 'logistica_etiqueta_tipos_producto';
	const HIDDEN_OPTION_KEY = 'logistica_etiqueta_tipos_ocultos';

	/**
	 * @return array<string, array{label:string}>
	 */
	public static function get_hidden() {
		$hidden = get_option( self::HIDDEN_OPTION_KEY, array() );

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

		$clean = array();
		foreach ( $hidden as $key => $entry ) {
			$key = self::normalize_key( $key );
			if ( '' === $key ) {
				continue;
			}

			if ( true === $entry || 1 === $entry || '1' === $entry ) {
				$clean[ $key ] = array( 'label' => '' );
				continue;
			}

			if ( ! is_array( $entry ) ) {
				continue;
			}

			$clean[ $key ] = array(
				'label' => trim( sanitize_text_field( (string) ( $entry['label'] ?? '' ) ) ),
			);
		}

		return $clean;
	}

	/**
	 * @param string $key Clave del tipo.
	 * @return bool
	 */
	public static function is_hidden( $key ) {
		$key = self::normalize_key( $key );

		return '' !== $key && isset( self::get_hidden()[ $key ] );
	}

	/**
	 * @param string $key   Clave del tipo.
	 * @param string $label Etiqueta visible al ocultar.
	 * @return void
	 */
	private static function hide_key( $key, $label = '' ) {
		$key = self::normalize_key( $key );
		if ( '' === $key ) {
			return;
		}

		$hidden         = self::get_hidden();
		$hidden[ $key ] = array(
			'label' => trim( sanitize_text_field( (string) $label ) ),
		);
		update_option( self::HIDDEN_OPTION_KEY, $hidden, false );
	}

	/**
	 * @param string $key Clave del tipo.
	 * @return void
	 */
	private static function unhide_key( $key ) {
		$key = self::normalize_key( $key );
		if ( '' === $key ) {
			return;
		}

		$hidden = self::get_hidden();
		if ( ! isset( $hidden[ $key ] ) ) {
			return;
		}

		unset( $hidden[ $key ] );
		update_option( self::HIDDEN_OPTION_KEY, $hidden, false );
	}

	/**
	 * @return array<string, array{label:string,type_code:string}>
	 */
	public static function get_saved() {
		$saved = get_option( self::OPTION_KEY, array() );

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

		$clean = array();
		foreach ( $saved as $key => $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$key = self::normalize_key( $key );
			if ( '' === $key ) {
				continue;
			}

			$label     = trim( sanitize_text_field( (string) ( $entry['label'] ?? '' ) ) );
			$type_code = self::normalize_type_code( $entry['type_code'] ?? '' );

			if ( '' === $label || '' === $type_code ) {
				continue;
			}

			$clean[ $key ] = array(
				'label'     => $label,
				'type_code' => $type_code,
			);
		}

		return $clean;
	}

	/**
	 * @return array<string, string> slug => etiqueta visible.
	 */
	public static function get_option_labels() {
		global $wpdb;

		$saved   = self::get_saved();
		$options = array();

		foreach ( Logistica_Label_Barcode::base_product_type_options() as $key => $default_label ) {
			if ( self::is_hidden( $key ) ) {
				continue;
			}
			$options[ $key ] = isset( $saved[ $key ]['label'] )
				? (string) $saved[ $key ]['label']
				: (string) $default_label;
		}

		foreach ( $saved as $key => $entry ) {
			if ( self::is_hidden( $key ) ) {
				continue;
			}
			$options[ $key ] = $entry['label'];
		}

		$table = Logistica_Database::table( 'etiqueta_productos' );
		if ( $table ) {
			$rows = $wpdb->get_col( "SELECT DISTINCT tipo_producto FROM {$table} WHERE tipo_producto <> '' ORDER BY tipo_producto ASC" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			if ( is_array( $rows ) ) {
				foreach ( $rows as $row ) {
					$key = self::normalize_key( $row );
					if ( '' === $key || self::is_hidden( $key ) || isset( $options[ $key ] ) ) {
						continue;
					}
					$options[ $key ] = ucwords( strtolower( str_replace( '_', ' ', $key ) ) );
				}
			}
		}

		uasort(
			$options,
			static function ( $a, $b ) {
				return strnatcasecmp( (string) $a, (string) $b );
			}
		);

		return $options;
	}

	/**
	 * @param mixed $label Nombre visible del producto.
	 * @return string
	 */
	public static function normalize_key( $label ) {
		$text = remove_accents( strtoupper( trim( (string) $label ) ) );
		$text = preg_replace( '/\s+/', ' ', $text );

		return trim( (string) $text );
	}

	/**
	 * Etiqueta visible: primera letra mayúscula, resto minúsculas.
	 *
	 * @param mixed $label Texto del nombre.
	 * @return string
	 */
	public static function normalize_display_label( $label ) {
		$label = trim( sanitize_text_field( (string) $label ) );
		if ( '' === $label ) {
			return '';
		}

		$lower = function_exists( 'mb_strtolower' ) ? mb_strtolower( $label, 'UTF-8' ) : strtolower( $label );
		$first = function_exists( 'mb_substr' ) ? mb_substr( $lower, 0, 1, 'UTF-8' ) : substr( $lower, 0, 1 );
		$rest  = function_exists( 'mb_substr' ) ? mb_substr( $lower, 1, null, 'UTF-8' ) : substr( $lower, 1 );
		$first = function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $first, 'UTF-8' ) : strtoupper( $first );

		return $first . $rest;
	}

	/**
	 * @param mixed $value Código de 2 dígitos.
	 * @return string
	 */
	public static function normalize_type_code( $value ) {
		$digits = preg_replace( '/\D+/', '', (string) $value );

		if ( 2 !== strlen( $digits ) ) {
			return '';
		}

		return $digits;
	}

	/**
	 * @return array<string, true>
	 */
	public static function used_type_codes() {
		$used = array();

		foreach ( Logistica_Label_Barcode::base_product_type_options() as $key => $label ) {
			unset( $label );
			if ( self::is_hidden( $key ) ) {
				continue;
			}
			$code = Logistica_Label_Barcode::built_in_type_code( $key );
			if ( '' !== $code ) {
				$used[ $code ] = true;
			}
		}

		foreach ( self::get_saved() as $entry ) {
			if ( ! empty( $entry['type_code'] ) ) {
				$used[ $entry['type_code'] ] = true;
			}
		}

		return $used;
	}

	/**
	 * Siguiente código EAN de tipo libre (consecutivo: 11, 12… tras el catálogo base hasta 10).
	 *
	 * @return string|null
	 */
	public static function next_type_code() {
		$used = self::used_type_codes();
		$max  = 0;

		foreach ( array_keys( $used ) as $code ) {
			$max = max( $max, (int) $code );
		}

		$next = $max + 1;
		if ( $next > 99 ) {
			return null;
		}

		return str_pad( (string) $next, 2, '0', STR_PAD_LEFT );
	}

	/**
	 * Filas para hoja de referencia Excel: etiqueta visible o clave => código 2 dígitos.
	 *
	 * @return array<int, array{0:string,1:string}>
	 */
	public static function excel_type_lookup_rows() {
		$rows = array();
		$seen = array();

		foreach ( self::get_option_labels() as $key => $label ) {
			$code = Logistica_Label_Barcode::type_code( $key );
			if ( '' === $code ) {
				continue;
			}

			foreach ( array( (string) $label, (string) $key ) as $lookup ) {
				$lookup = trim( $lookup );
				if ( '' === $lookup ) {
					continue;
				}

				$seen_key = strtolower( $lookup );
				if ( isset( $seen[ $seen_key ] ) ) {
					continue;
				}

				$seen[ $seen_key ] = true;
				$rows[]            = array( $lookup, $code );
			}
		}

		usort(
			$rows,
			static function ( $a, $b ) {
				return strnatcasecmp( (string) $a[0], (string) $b[0] );
			}
		);

		return $rows;
	}

	/**
	 * @param string $tipo Tipo normalizado.
	 * @return string Código EAN de 2 dígitos o vacío.
	 */
	public static function type_code_for( $tipo ) {
		$key = self::normalize_key( $tipo );
		if ( '' === $key ) {
			return '';
		}

		$saved = self::get_saved();

		return isset( $saved[ $key ]['type_code'] ) ? (string) $saved[ $key ]['type_code'] : '';
	}

	/**
	 * @param mixed  $label     Nombre del producto.
	 * @param mixed  $type_code Código EAN de 2 dígitos (opcional).
	 * @return array{key:string,label:string,type_code:string}|\WP_Error
	 */
	/**
	 * @return list<array{key:string,label:string,type_code:string,built_in:bool,can_delete:bool}>
	 */
	public static function get_admin_entries() {
		$entries = array();
		$seen    = array();
		$saved   = self::get_saved();
		$base    = Logistica_Label_Barcode::base_product_type_options();

		foreach ( $base as $key => $default_label ) {
			if ( self::is_hidden( $key ) ) {
				continue;
			}

			$entries[] = array(
				'key'        => $key,
				'label'      => isset( $saved[ $key ]['label'] ) ? (string) $saved[ $key ]['label'] : (string) $default_label,
				'type_code'  => Logistica_Label_Barcode::built_in_type_code( $key ),
				'built_in'   => true,
				'can_delete' => true,
			);
			$seen[ $key ] = true;
		}

		foreach ( $saved as $key => $entry ) {
			if ( isset( $seen[ $key ] ) || self::is_hidden( $key ) ) {
				continue;
			}

			$entries[] = array(
				'key'        => $key,
				'label'      => (string) $entry['label'],
				'type_code'  => (string) $entry['type_code'],
				'built_in'   => false,
				'can_delete' => true,
			);
			$seen[ $key ] = true;
		}

		foreach ( self::get_option_labels() as $key => $label ) {
			if ( isset( $seen[ $key ] ) || self::is_hidden( $key ) ) {
				continue;
			}

			$code = self::type_code_for( $key );
			if ( '' === $code ) {
				$code = Logistica_Label_Barcode::built_in_type_code( $key );
			}

			$entries[] = array(
				'key'        => $key,
				'label'      => (string) $label,
				'type_code'  => (string) $code,
				'built_in'   => false,
				'can_delete' => true,
			);
			$seen[ $key ] = true;
		}

		usort(
			$entries,
			static function ( $a, $b ) {
				return strnatcasecmp( (string) $a['label'], (string) $b['label'] );
			}
		);

		return $entries;
	}

	/**
	 * @param string $key         Clave del tipo.
	 * @param mixed  $label       Etiqueta visible.
	 * @param mixed  $type_code   Código EAN de 2 dígitos (solo tipos personalizados).
	 * @return array{key:string,label:string,type_code:string}|\WP_Error
	 */
	public static function update( $key, $label, $type_code = null ) {
		$key   = self::normalize_key( $key );
		$label = self::normalize_display_label( $label );

		if ( '' === $key || '' === $label ) {
			return new WP_Error( 'invalid_tipo', __( 'Indica el nombre del producto.', 'logistica' ) );
		}

		$base     = Logistica_Label_Barcode::base_product_type_options();
		$built_in = isset( $base[ $key ] );
		$saved    = self::get_saved();

		if ( $built_in ) {
			$type_code = Logistica_Label_Barcode::built_in_type_code( $key );
		} else {
			$existing_code = (string) ( $saved[ $key ]['type_code'] ?? '' );
			if ( '' === $existing_code ) {
				$existing_code = (string) Logistica_Label_Barcode::type_code( $key );
			}

			if ( '' !== $existing_code ) {
				$type_code = $existing_code;
			} else {
				$type_code = self::normalize_type_code( null !== $type_code ? $type_code : '' );
				if ( '' === $type_code ) {
					return new WP_Error( 'no_type_code', __( 'Indica el código EAN de tipo (2 dígitos).', 'logistica' ) );
				}

				$used = self::used_type_codes();
				if ( ! empty( $used[ $type_code ] ) ) {
					return new WP_Error( 'duplicate_type_code', __( 'Ese código EAN de tipo ya está en uso.', 'logistica' ) );
				}
			}
		}

		if ( '' === $type_code ) {
			return new WP_Error( 'no_type_code', __( 'No se pudo determinar el código EAN de tipo.', 'logistica' ) );
		}

		self::unhide_key( $key );

		$saved[ $key ] = array(
			'label'     => $label,
			'type_code' => (string) $type_code,
		);
		update_option( self::OPTION_KEY, $saved, false );

		return array(
			'key'       => $key,
			'label'     => $label,
			'type_code' => (string) $type_code,
		);
	}

	/**
	 * @param string $key Clave del tipo personalizado.
	 * @return true|\WP_Error
	 */
	public static function remove( $key ) {
		$key = self::normalize_key( $key );

		if ( '' === $key ) {
			return new WP_Error( 'invalid_tipo', __( 'Indica el tipo de producto.', 'logistica' ) );
		}

		if ( self::is_hidden( $key ) ) {
			return new WP_Error( 'already_hidden', __( 'El tipo ya está oculto.', 'logistica' ) );
		}

		$label_snapshot = (string) ( self::get_option_labels()[ $key ] ?? '' );
		$saved          = self::get_saved();
		$saved_entry    = isset( $saved[ $key ] ) ? $saved[ $key ] : null;

		if ( isset( $saved[ $key ] ) ) {
			unset( $saved[ $key ] );
			update_option( self::OPTION_KEY, $saved, false );
		}

		self::hide_key( $key, $label_snapshot );

		Logistica_Label_Option_Lifecycle::record_remove(
			'tipo',
			$key,
			$label_snapshot ?: $key,
			array(
				'key'    => $key,
				'label'  => $label_snapshot,
				'hidden' => array( 'label' => $label_snapshot ),
				'saved'  => $saved_entry,
			)
		);

		return true;
	}

	/**
	 * Restaura una opción desde snapshot de papelera (sin duplicar auditoría de UI local).
	 *
	 * @param array<string, mixed> $payload Snapshot.
	 * @return true|\WP_Error
	 */
	public static function apply_restore_from_payload( $payload ) {
		$key = self::normalize_key( (string) ( $payload['key'] ?? '' ) );
		if ( '' === $key ) {
			return new WP_Error( 'invalid_tipo', __( 'No hay clave de tipo para restaurar.', 'logistica' ) );
		}

		if ( ! empty( $payload['saved'] ) && is_array( $payload['saved'] ) ) {
			$saved = self::get_saved();
			$entry = $payload['saved'];
			$label = trim( sanitize_text_field( (string) ( $entry['label'] ?? '' ) ) );
			$code  = self::normalize_type_code( $entry['type_code'] ?? '' );
			if ( '' !== $label && '' !== $code ) {
				$saved[ $key ] = array(
					'label'     => $label,
					'type_code' => $code,
				);
				update_option( self::OPTION_KEY, $saved, false );
			}
		}

		self::unhide_key( $key );

		return true;
	}

	/**
	 * @param string $key Clave del tipo oculto.
	 * @return true|\WP_Error
	 */
	public static function restore( $key ) {
		$key = self::normalize_key( $key );

		if ( '' === $key || ! self::is_hidden( $key ) ) {
			return new WP_Error( 'not_hidden', __( 'El tipo no está en la lista de ocultos.', 'logistica' ) );
		}

		self::unhide_key( $key );

		Logistica_Label_Option_Lifecycle::record_restore( 'tipo', $key, (string) ( self::get_option_labels()[ $key ] ?? $key ) );

		return true;
	}

	/**
	 * @return list<array{kind:string,key:string,label:string,meta:string,usage:int}>
	 */
	public static function get_hidden_admin_entries() {
		$entries = array();
		$base    = Logistica_Label_Barcode::base_product_type_options();
		$hidden  = self::get_hidden();

		foreach ( $hidden as $key => $meta ) {
			$label = trim( (string) ( $meta['label'] ?? '' ) );
			if ( '' === $label ) {
				$label = isset( $base[ $key ] ) ? (string) $base[ $key ] : ucwords( strtolower( str_replace( '_', ' ', $key ) ) );
			}

			$type_code = self::type_code_for( $key );
			if ( '' === $type_code ) {
				$type_code = Logistica_Label_Barcode::built_in_type_code( $key );
			}

			$entries[] = array(
				'kind'  => 'tipo',
				'key'   => (string) $key,
				'label' => $label,
				'meta'  => (string) $type_code,
				'usage' => Logistica_Label_Etiqueta_Option_Usage::count_tipo( $key ),
			);
		}

		usort(
			$entries,
			static function ( $a, $b ) {
				return strnatcasecmp( (string) $a['label'], (string) $b['label'] );
			}
		);

		return $entries;
	}

	public static function add( $label, $type_code = '' ) {
		$label = self::normalize_display_label( $label );
		$key   = self::normalize_key( $label );

		if ( '' === $label || '' === $key ) {
			return new WP_Error( 'empty_tipo', __( 'Indica el nombre del producto.', 'logistica' ) );
		}

		$base = Logistica_Label_Barcode::base_product_type_options();
		if ( isset( $base[ $key ] ) && self::is_hidden( $key ) ) {
			self::unhide_key( $key );

			return self::update( $key, $label );
		}

		$options = self::get_option_labels();
		if ( isset( $options[ $key ] ) ) {
			return array(
				'key'       => $key,
				'label'     => (string) $options[ $key ],
				'type_code' => self::type_code_for( $key ) ?: Logistica_Label_Barcode::built_in_type_code( $key ),
			);
		}

		self::unhide_key( $key );

		$type_code = self::normalize_type_code( $type_code );
		if ( '' === $type_code ) {
			$type_code = self::next_type_code();
		}

		if ( null === $type_code || '' === $type_code ) {
			return new WP_Error( 'no_type_code', __( 'No hay códigos EAN de tipo disponibles.', 'logistica' ) );
		}

		$used = self::used_type_codes();
		if ( ! empty( $used[ $type_code ] ) ) {
			return new WP_Error( 'duplicate_type_code', __( 'Ese código EAN de tipo ya está en uso.', 'logistica' ) );
		}

		$saved         = self::get_saved();
		$saved[ $key ] = array(
			'label'     => $label,
			'type_code' => $type_code,
		);

		update_option( self::OPTION_KEY, $saved, false );

		return array(
			'key'       => $key,
			'label'     => $label,
			'type_code' => $type_code,
		);
	}
}
