<?php
/**
 * Catálogos reutilizables de opciones para selects del panel.
 *
 * @package Logistica
 */

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

/**
 * CRUD genérico key=>label o lista de etiquetas, persistido en wp_options.
 */
class Logistica_Select_Catalog {

	const OPTION_PREFIX = 'logistica_select_catalog_';

	/**
	 * Definiciones de catálogos disponibles.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function definitions() {
		static $defs = null;

		if ( null !== $defs ) {
			return $defs;
		}

		$defs = array(
			'tapas_status'            => array(
				'title'       => __( 'Estados (tapas / bolsas)', 'logistica' ),
				'description' => __( 'Opciones del campo Estado en Cálculo tapas y Cálculos bolsas.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'pendiente'   => 'Pendiente',
					'fabricacion' => 'Fabricación',
					'formado'     => 'Formado',
					'finalizado'  => 'Finalizado',
					'cancelado'   => 'Cancelado',
					'otro'        => 'Otro...',
				),
			),
			'rotulo_tipos'            => array(
				'title'       => __( 'Tipos de rótulo', 'logistica' ),
				'description' => __( 'Opciones del select Tipo en Rótulos rollos.', 'logistica' ),
				'mode'        => 'map',
				'case'        => 'upper',
				'defaults'    => array(
					'ROLLO'  => 'Rollo',
					'FONDOS' => 'Fondos',
				),
			),
			'rotulo_tamanos'          => array(
				'title'       => __( 'Tamaños de rótulo', 'logistica' ),
				'description' => __( 'Lista sugerida de tamaños para Rótulos rollos (también usable como select).', 'logistica' ),
				'mode'        => 'list',
				'case'        => 'upper',
				'defaults'    => array( '68 CM', '85 MM', '100 CM' ),
			),
			'closing_base_types'      => array(
				'title'       => __( 'Tipos de base (cierre)', 'logistica' ),
				'description' => __( 'Select de tipo de base en Cierre de órdenes y Características. Marca qué campos muestra cada tipo (código, lote y ajuste siempre visibles).', 'logistica' ),
				'mode'        => 'map',
				'fields'      => true,
				'defaults'    => array(
					'cajas'          => 'Cajas',
					'cajas_saldo'    => 'Cajas saldo',
					'bolsas'         => 'Bolsas',
					'rollos'         => 'Rollo fondo',
					'plastico'       => 'Plástico',
					'acetato'        => 'Acetato (láminas)',
					'papel_hojas'    => 'Papel en hojas',
					'papel_bobinas'  => 'Papel en bobinas',
					'microcorrugado' => 'Microcorrugado',
				),
			),
			'closing_variants'        => array(
				'title'       => __( 'Variantes (cierre)', 'logistica' ),
				'description' => __( 'Variantes estándar del catálogo de cierre de órdenes.', 'logistica' ),
				'mode'        => 'list',
				'defaults'    => array( 'Nacional', 'Exportación', 'Bionatura', 'Estándar', 'Pan pa ya' ),
			),
			'closing_product_types'   => array(
				'title'       => __( 'Tipos de producto (cierre)', 'logistica' ),
				'description' => __( 'Tipos de producto estándar del catálogo de cierre.', 'logistica' ),
				'mode'        => 'list',
				'defaults'    => array( 'Vaso', 'Tazon/Bowl', 'Tapa', 'Collarin', 'Acetato', 'Otro' ),
			),
			'closing_references'      => array(
				'title'       => __( 'Referencias (cierre)', 'logistica' ),
				'description' => __( 'Nombres de referencia del cierre de órdenes. Se eligen en Operaciones; créalas o edítalas aquí.', 'logistica' ),
				'mode'        => 'list',
				'case'        => 'upper',
				'defaults'    => array(),
			),
			'minuta_revision'         => array(
				'title'       => __( 'Estados de revisión (minuta)', 'logistica' ),
				'description' => __( 'Opciones del campo Revisión en Revisión minuta.', 'logistica' ),
				'mode'        => 'list',
				'defaults'    => array( 'Pendiente', 'OK', 'Con observacion' ),
			),
			'pedidos_orden_estado'    => array(
				'title'       => __( 'Estados de orden de compra', 'logistica' ),
				'description' => __( 'Select Estado en Cuadro de pedidos.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'sin_confirmar' => 'Sin confirmar',
					'aprobado'      => 'Aprobado',
					'cancelado'     => 'Cancelado',
					'programado'    => 'Programado',
					'conversion'    => 'Conversión',
					'impresion'     => 'Impresión',
					'troquelado'    => 'Troquelado',
					'descartone'    => 'Descartone',
					'formado'       => 'Formado',
					'embalaje'      => 'Embalaje',
					'finalizado'    => 'Finalizado',
					'despacho'      => 'Despacho',
					'entregado'     => 'Entregado',
				),
			),
			'priorities'              => array(
				'title'       => __( 'Prioridades', 'logistica' ),
				'description' => __( 'Prioridades de agenda y cotizaciones en Inicio.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'baja'  => 'Baja',
					'media' => 'Media',
					'alta'  => 'Alta',
				),
			),
			'agenda_statuses'         => array(
				'title'       => __( 'Estados de agenda', 'logistica' ),
				'description' => __( 'Estados del select al crear o modificar actividades en Agenda de Inicio. Usa la clave «completado» para marcar finalizadas.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'pendiente'   => 'Pendiente',
					'en_proceso'  => 'En proceso',
					'retrasado'   => 'Retrasado',
					'completado'  => 'Completado',
				),
			),
			'day_types'               => array(
				'title'       => __( 'Tipos de día', 'logistica' ),
				'description' => __( 'Tipos de día del checklist y planeación. La clave vacía representa «Laborable».', 'logistica' ),
				'mode'        => 'map',
				'allow_empty_key' => true,
				'defaults'    => array(
					''           => 'Laborable',
					'festivo'    => 'Festivo',
					'libre'      => 'Día libre',
					'vacaciones' => 'Vacaciones',
					'permisos'   => 'Permisos',
				),
			),
			'planeacion_statuses'     => array(
				'title'       => __( 'Estados de tarea (planeación)', 'logistica' ),
				'description' => __( 'Avance interno en Planeación impresión (Pendiente / En proceso / …). En formado, el Estado visible es el del cuadro de pedidos.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'pending'     => 'Pendiente',
					'in_progress' => 'En proceso',
					'done'        => 'Terminado',
					'delayed'     => 'Retrasado',
				),
			),
			'planeacion_labor_types'  => array(
				'title'       => __( 'Tipos de labor (planeación)', 'logistica' ),
				'description' => __( 'Tipos de labor en Planeación impresión / formado.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'impresion'              => 'Impresión',
					'prep_tinta'             => 'Preparación de tinta',
					'limpieza_mantenimiento' => 'Limpieza y mantenimiento',
					'prep_sig_trabajo'       => 'Preparación siguiente trabajo',
					'lavado_maquina'         => 'Lavado de máquina',
					'reunion'                => 'Reunión impresión y diseño',
					'otro'                   => 'Otra actividad',
				),
			),
			'planeacion_complexity'   => array(
				'title'       => __( 'Complejidad (planeación)', 'logistica' ),
				'description' => __( 'Niveles de complejidad en Planeación.', 'logistica' ),
				'mode'        => 'map',
				'defaults'    => array(
					'normal'   => 'Normal',
					'media'    => 'Media',
					'alta'     => 'Alta',
					'muy_alta' => 'Muy alta',
				),
			),
			'compra_statuses'         => array(
				'title'       => __( 'Estados de cotización / compra', 'logistica' ),
				'description' => __( 'Estados del select al crear o modificar una cotización en Inicio.', 'logistica' ),
				'mode'        => 'map',
				'external'    => 'compra_statuses',
				'defaults'    => array(),
			),
		);

		return $defs;
	}

	/**
	 * @param string $catalog_id Catalog id.
	 * @return array<string, mixed>|null
	 */
	public static function get_definition( $catalog_id ) {
		$catalog_id = sanitize_key( (string) $catalog_id );
		$defs       = self::definitions();

		return isset( $defs[ $catalog_id ] ) ? $defs[ $catalog_id ] : null;
	}

	/**
	 * @param string $catalog_id Catalog id.
	 * @return bool
	 */
	public static function exists( $catalog_id ) {
		return null !== self::get_definition( $catalog_id );
	}

	/**
	 * @param string $catalog_id Catalog id.
	 * @return string
	 */
	public static function option_key( $catalog_id ) {
		return self::OPTION_PREFIX . sanitize_key( (string) $catalog_id );
	}

	/**
	 * Opciones visibles (key => label o lista).
	 *
	 * @param string $catalog_id Catalog id.
	 * @return array<string, string>|list<string>
	 */
	public static function get_options( $catalog_id ) {
		$def = self::get_definition( $catalog_id );
		if ( ! $def ) {
			return array();
		}

		if ( ! empty( $def['external'] ) && 'compra_statuses' === $def['external'] ) {
			return Logistica_Settings::get_compra_statuses();
		}

		$saved = get_option( self::option_key( $catalog_id ), null );
		if ( ! is_array( $saved ) || empty( $saved ) ) {
			$defaults = $def['defaults'] ?? array();
			if ( 'closing_references' === $catalog_id && empty( $defaults ) && class_exists( 'Logistica_Order_Closing', false ) ) {
				$defaults = Logistica_Order_Closing::catalog_reference_name_defaults();
			}

			return self::normalize_options( $catalog_id, $defaults );
		}

		return self::normalize_options( $catalog_id, $saved );
	}

	/**
	 * Entradas para la UI de administración.
	 *
	 * @param string $catalog_id Catalog id.
	 * @return list<array{key:string,label:string,can_delete:bool}>
	 */
	public static function get_admin_entries( $catalog_id ) {
		$options = self::get_options( $catalog_id );
		$def     = self::get_definition( $catalog_id );
		$mode    = $def['mode'] ?? 'map';
		$entries = array();

		if ( 'list' === $mode ) {
			foreach ( $options as $label ) {
				$label = (string) $label;
				$entries[] = array(
					'key'        => $label,
					'label'      => $label,
					'can_delete' => true,
				);
			}
			return $entries;
		}

		foreach ( $options as $key => $label ) {
			$entries[] = array(
				'key'        => (string) $key,
				'label'      => (string) $label,
				'can_delete' => true,
			);
		}

		return $entries;
	}

	/**
	 * @param string               $catalog_id Catalog id.
	 * @param array<string,mixed>|list<mixed> $raw Raw options.
	 * @return array<string, string>|list<string>
	 */
	public static function normalize_options( $catalog_id, $raw ) {
		$def  = self::get_definition( $catalog_id );
		$mode = $def['mode'] ?? 'map';
		$case = $def['case'] ?? '';
		$allow_empty = ! empty( $def['allow_empty_key'] );

		if ( 'list' === $mode ) {
			$clean = array();
			$seen  = array();
			foreach ( (array) $raw as $item ) {
				$label = self::normalize_label( $item, $case );
				if ( '' === $label ) {
					continue;
				}
				$lookup = strtolower( $label );
				if ( isset( $seen[ $lookup ] ) ) {
					continue;
				}
				$seen[ $lookup ] = true;
				$clean[]         = $label;
			}
			return array_values( $clean );
		}

		$clean = array();
		foreach ( (array) $raw as $key => $label ) {
			$key   = self::normalize_key( $key, $allow_empty );
			$label = self::normalize_label( $label, $case );
			if ( ( '' === $key && ! $allow_empty ) || '' === $label ) {
				continue;
			}
			if ( '' === $key && $allow_empty ) {
				$clean[''] = $label;
				continue;
			}
			$clean[ $key ] = $label;
		}

		return $clean;
	}

	/**
	 * @param mixed  $key Key.
	 * @param bool   $allow_empty Allow empty key.
	 * @return string
	 */
	public static function normalize_key( $key, $allow_empty = false ) {
		$key = (string) $key;
		if ( '__empty__' === $key || '__laborable__' === $key ) {
			return $allow_empty ? '' : sanitize_key( $key );
		}
		$key = sanitize_key( $key );
		if ( '' === $key && $allow_empty ) {
			return '';
		}
		return $key;
	}

	/**
	 * @param mixed  $label Label.
	 * @param string $case  upper|sentence|.
	 * @return string
	 */
	public static function normalize_label( $label, $case = '' ) {
		$label = trim( sanitize_text_field( (string) $label ) );
		if ( '' === $label ) {
			return '';
		}

		if ( 'upper' === $case ) {
			return function_exists( 'mb_strtoupper' )
				? mb_strtoupper( $label, 'UTF-8' )
				: strtoupper( $label );
		}

		return $label;
	}

	/**
	 * Persiste opciones completas.
	 *
	 * @param string $catalog_id Catalog id.
	 * @param mixed  $raw Raw options.
	 * @return array<string, string>|list<string>|\WP_Error
	 */
	public static function save_options( $catalog_id, $raw ) {
		$def = self::get_definition( $catalog_id );
		if ( ! $def ) {
			return new WP_Error( 'invalid_catalog', __( 'Catálogo no válido.', 'logistica' ) );
		}

		if ( ! empty( $def['external'] ) && 'compra_statuses' === $def['external'] ) {
			$rows = array();
			$normalized = self::normalize_options( $catalog_id, $raw );
			foreach ( $normalized as $key => $label ) {
				$rows[] = array(
					'key'   => $key,
					'label' => $label,
				);
			}
			Logistica_Settings::save_compra_statuses( $rows );
			return Logistica_Settings::get_compra_statuses();
		}

		$options = self::normalize_options( $catalog_id, $raw );
		if ( empty( $options ) ) {
			return new WP_Error( 'empty_catalog', __( 'El catálogo no puede quedar vacío.', 'logistica' ) );
		}

		update_option( self::option_key( $catalog_id ), $options, false );

		return $options;
	}

	/**
	 * Agrega una opción.
	 *
	 * @param string $catalog_id Catalog id.
	 * @param string $label      Label.
	 * @param string $key        Optional key (map mode).
	 * @return array{key:string,label:string}|\WP_Error
	 */
	public static function add( $catalog_id, $label, $key = '' ) {
		$def = self::get_definition( $catalog_id );
		if ( ! $def ) {
			return new WP_Error( 'invalid_catalog', __( 'Catálogo no válido.', 'logistica' ) );
		}

		$case        = $def['case'] ?? '';
		$mode        = $def['mode'] ?? 'map';
		$allow_empty = ! empty( $def['allow_empty_key'] );
		$label       = self::normalize_label( $label, $case );

		if ( '' === $label ) {
			return new WP_Error( 'missing_label', __( 'El nombre es obligatorio.', 'logistica' ) );
		}

		$options = self::get_options( $catalog_id );

		if ( 'list' === $mode ) {
			foreach ( $options as $existing ) {
				if ( strtolower( (string) $existing ) === strtolower( $label ) ) {
					return new WP_Error( 'duplicate', __( 'Esa opción ya existe.', 'logistica' ) );
				}
			}
			$options[] = $label;
			$result    = self::save_options( $catalog_id, $options );
			if ( is_wp_error( $result ) ) {
				return $result;
			}
			return array(
				'key'   => $label,
				'label' => $label,
			);
		}

		$key = '' !== trim( (string) $key )
			? self::normalize_key( $key, $allow_empty )
			: self::normalize_key( remove_accents( $label ), $allow_empty );

		if ( '' === $key && ! $allow_empty ) {
			return new WP_Error( 'missing_key', __( 'No se pudo generar la clave interna.', 'logistica' ) );
		}

		if ( array_key_exists( $key, $options ) ) {
			return new WP_Error( 'duplicate', __( 'Esa clave ya existe.', 'logistica' ) );
		}

		$options[ $key ] = $label;
		$result          = self::save_options( $catalog_id, $options );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

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

	/**
	 * Actualiza una opción.
	 *
	 * @param string $catalog_id Catalog id.
	 * @param string $key        Current key / label.
	 * @param string $label      New label.
	 * @param string $new_key    Optional new key (map mode, usually ignored).
	 * @return array{key:string,label:string}|\WP_Error
	 */
	public static function update( $catalog_id, $key, $label, $new_key = '' ) {
		$def = self::get_definition( $catalog_id );
		if ( ! $def ) {
			return new WP_Error( 'invalid_catalog', __( 'Catálogo no válido.', 'logistica' ) );
		}

		$case        = $def['case'] ?? '';
		$mode        = $def['mode'] ?? 'map';
		$allow_empty = ! empty( $def['allow_empty_key'] );
		$label       = self::normalize_label( $label, $case );

		if ( '' === $label ) {
			return new WP_Error( 'missing_label', __( 'El nombre es obligatorio.', 'logistica' ) );
		}

		$options = self::get_options( $catalog_id );

		if ( 'list' === $mode ) {
			$old     = self::normalize_label( $key, $case );
			$found   = false;
			$updated = array();
			foreach ( $options as $existing ) {
				if ( strtolower( (string) $existing ) === strtolower( $old ) ) {
					$found     = true;
					$updated[] = $label;
					continue;
				}
				if ( strtolower( (string) $existing ) === strtolower( $label ) ) {
					return new WP_Error( 'duplicate', __( 'Esa opción ya existe.', 'logistica' ) );
				}
				$updated[] = $existing;
			}
			if ( ! $found ) {
				return new WP_Error( 'not_found', __( 'Opción no encontrada.', 'logistica' ) );
			}
			$result = self::save_options( $catalog_id, $updated );
			if ( is_wp_error( $result ) ) {
				return $result;
			}

			$propagated = 0;
			if ( class_exists( 'Logistica_Order_Closing', false ) ) {
				$propagated = Logistica_Order_Closing::propagate_catalog_rename( $catalog_id, $old, $label );
			}

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

		$key = self::normalize_key( $key, $allow_empty );
		if ( ! array_key_exists( $key, $options ) ) {
			return new WP_Error( 'not_found', __( 'Opción no encontrada.', 'logistica' ) );
		}

		$options[ $key ] = $label;
		unset( $new_key );

		$result = self::save_options( $catalog_id, $options );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

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

	/**
	 * Elimina una opción.
	 *
	 * @param string $catalog_id Catalog id.
	 * @param string $key        Key / label.
	 * @return true|\WP_Error
	 */
	public static function remove( $catalog_id, $key ) {
		$def = self::get_definition( $catalog_id );
		if ( ! $def ) {
			return new WP_Error( 'invalid_catalog', __( 'Catálogo no válido.', 'logistica' ) );
		}

		$mode        = $def['mode'] ?? 'map';
		$case        = $def['case'] ?? '';
		$allow_empty = ! empty( $def['allow_empty_key'] );
		$options     = self::get_options( $catalog_id );

		if ( 'list' === $mode ) {
			$target  = self::normalize_label( $key, $case );
			$updated = array();
			$found   = false;
			foreach ( $options as $existing ) {
				if ( strtolower( (string) $existing ) === strtolower( $target ) ) {
					$found = true;
					continue;
				}
				$updated[] = $existing;
			}
			if ( ! $found ) {
				return new WP_Error( 'not_found', __( 'Opción no encontrada.', 'logistica' ) );
			}
			if ( empty( $updated ) ) {
				return new WP_Error( 'empty_catalog', __( 'El catálogo no puede quedar vacío.', 'logistica' ) );
			}
			$result = self::save_options( $catalog_id, $updated );
			return is_wp_error( $result ) ? $result : true;
		}

		$key = self::normalize_key( $key, $allow_empty );
		if ( ! array_key_exists( $key, $options ) ) {
			return new WP_Error( 'not_found', __( 'Opción no encontrada.', 'logistica' ) );
		}

		unset( $options[ $key ] );
		if ( empty( $options ) ) {
			return new WP_Error( 'empty_catalog', __( 'El catálogo no puede quedar vacío.', 'logistica' ) );
		}

		$result = self::save_options( $catalog_id, $options );
		return is_wp_error( $result ) ? $result : true;
	}

	/**
	 * Grupos de catálogos por pestaña de configuración.
	 *
	 * @return array<string, list<string>>
	 */
	public static function catalogs_by_config_tab() {
		return array(
			'logistica'                        => array( 'compra_statuses', 'agenda_statuses', 'priorities', 'day_types' ),
			'logistica-calculo-tapas'           => array( 'tapas_status' ),
			'logistica-calculos'                => array( 'tapas_status' ),
			'logistica-cierre-ordenes'          => array( 'closing_base_types', 'closing_variants', 'closing_product_types', 'closing_references' ),
			'logistica-cierre-caracteristicas'  => array( 'closing_base_types' ),
			'logistica-revision-minuta'         => array( 'minuta_revision' ),
			'logistica-rotulos-rollos'          => array( 'rotulo_tipos', 'rotulo_tamanos' ),
			'logistica-cuadro-pedidos'          => array( 'pedidos_orden_estado' ),
			'logistica-planeacion-impresion'    => array( 'planeacion_statuses', 'planeacion_labor_types', 'planeacion_complexity', 'day_types' ),
			'logistica-planeacion-formado'      => array( 'planeacion_labor_types', 'planeacion_complexity', 'day_types' ),
		);
	}
}
