<?php
/**
 * Tablero de producción en tiempo real (día actual).
 *
 * @package Logistica
 */

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

/**
 * Agrega tareas de planeación (impresión + formado) para pantallas tipo TV.
 */
final class Logistica_Production_Board {

	public const POLL_SECONDS = 15;

	/**
	 * Prioridades Plan A/B/C.
	 *
	 * @return array<string, string>
	 */
	public static function get_priorities() {
		return array(
			'plan_a' => __( 'Plan A — Urgente', 'logistica' ),
			'plan_b' => __( 'Plan B — Normal', 'logistica' ),
			'plan_c' => __( 'Plan C — Baja', 'logistica' ),
		);
	}

	/**
	 * Novedades / alertas de producción.
	 *
	 * @return array<string, string>
	 */
	public static function get_alerts() {
		return array(
			''                   => __( 'Sin novedad', 'logistica' ),
			'programado'         => __( 'Programado', 'logistica' ),
			'en_proceso'         => __( 'En proceso', 'logistica' ),
			'problemas_filtrado' => __( 'Problemas de filtrado', 'logistica' ),
			'mantenimiento'      => __( 'Mantenimiento', 'logistica' ),
			'revision_material'  => __( 'Revisión de material', 'logistica' ),
			'cambio_referencia'  => __( 'Cambio de referencia', 'logistica' ),
			'detenido'           => __( 'Detenido', 'logistica' ),
			'finalizado'         => __( 'Finalizado', 'logistica' ),
		);
	}

	/**
	 * Columnas / tableros del día (etapa de planta).
	 *
	 * @return array<string, string>
	 */
	public static function get_lanes() {
		return array(
			'impresion'   => __( 'Impresión', 'logistica' ),
			'conversion'  => __( 'Conversión', 'logistica' ),
			'troquelado'  => __( 'Troquelado', 'logistica' ),
			'descartone'  => __( 'Descartone', 'logistica' ),
			'formado'     => __( 'Formado', 'logistica' ),
			'embalaje'    => __( 'Embalaje', 'logistica' ),
			'programado'  => __( 'Programado', 'logistica' ),
			'otros'       => __( 'Otros', 'logistica' ),
		);
	}

	/**
	 * @param string $value Prioridad.
	 * @return string
	 */
	public static function sanitize_priority( $value ) {
		$key = sanitize_key( (string) $value );

		return isset( self::get_priorities()[ $key ] ) ? $key : 'plan_b';
	}

	/**
	 * @param string $value Alerta.
	 * @return string
	 */
	public static function sanitize_alert( $value ) {
		$key = sanitize_key( (string) $value );

		return array_key_exists( $key, self::get_alerts() ) ? $key : '';
	}

	/**
	 * Día 0–6 de la semana (lunes=0) para una fecha Y-m-d.
	 *
	 * @param string $date Y-m-d.
	 * @return int
	 */
	public static function day_index_for_date( $date ) {
		$ts = strtotime( $date . ' 12:00:00' );
		if ( ! $ts ) {
			$ts = current_time( 'timestamp' );
		}
		$iso = (int) wp_date( 'N', $ts );

		return max( 0, min( 6, $iso - 1 ) );
	}

	/**
	 * Minutos desde medianoche (Bogotá).
	 *
	 * @return int
	 */
	public static function now_minutes() {
		return ( (int) wp_date( 'G' ) * 60 ) + (int) wp_date( 'i' );
	}

	/**
	 * Resuelve en qué columna va la tarjeta.
	 *
	 * @param array<string, mixed> $task Tarea.
	 * @param string               $pedido_estado Estado de línea OC (opcional).
	 * @return string
	 */
	public static function resolve_lane( $task, $pedido_estado = '' ) {
		$lanes = self::get_lanes();
		$estado = sanitize_key( (string) $pedido_estado );
		if ( '' !== $estado && isset( $lanes[ $estado ] ) ) {
			return $estado;
		}

		$labor = sanitize_key( (string) ( $task['labor_type'] ?? '' ) );
		$map   = array(
			'impresion'             => 'impresion',
			'troquelado'            => 'troquelado',
			'formado'               => 'formado',
			'pegado'                => 'embalaje',
			'corte'                 => 'otros',
			'prep_tinta'            => 'impresion',
			'prep_sig_trabajo'      => 'programado',
			'limpieza_mantenimiento'=> 'otros',
			'lavado_maquina'        => 'otros',
			'reunion'               => 'otros',
			'otro'                  => 'otros',
		);

		if ( isset( $map[ $labor ] ) ) {
			return $map[ $labor ];
		}

		$status = sanitize_key( (string) ( $task['status'] ?? '' ) );
		if ( 'done' === $status ) {
			return 'embalaje';
		}
		if ( in_array( $status, array( 'pending' ), true ) ) {
			return 'programado';
		}

		return 'otros';
	}

	/**
	 * Deriva alerta visible si no hay novedad manual.
	 *
	 * @param array<string, mixed> $task Tarea.
	 * @param int                  $now_minutes Ahora.
	 * @return string
	 */
	public static function resolve_alert( $task, $now_minutes ) {
		$manual = self::sanitize_alert( $task['board_alert'] ?? '' );
		if ( '' !== $manual ) {
			return $manual;
		}

		$status = sanitize_key( (string) ( $task['status'] ?? 'pending' ) );
		if ( 'done' === $status ) {
			return 'finalizado';
		}
		if ( 'delayed' === $status ) {
			return 'detenido';
		}
		if ( 'in_progress' === $status ) {
			return 'en_proceso';
		}

		$start = isset( $task['actual_start'] ) && null !== $task['actual_start'] && '' !== $task['actual_start']
			? (int) $task['actual_start']
			: (int) ( $task['start_minutes'] ?? 0 );
		$duration = max( 0, (int) ( $task['duration_minutes'] ?? 0 ) );
		$end      = $start + $duration;

		if ( $duration > 0 && $now_minutes > $end && 'done' !== $status ) {
			return 'detenido';
		}
		if ( $now_minutes >= $start && $now_minutes < $end ) {
			return 'en_proceso';
		}

		return 'programado';
	}

	/**
	 * Minutos de reloj → HH:MM.
	 *
	 * @param int|null $minutes Minutos.
	 * @return string
	 */
	public static function format_clock( $minutes ) {
		if ( null === $minutes || '' === $minutes ) {
			return '—';
		}
		$minutes = max( 0, (int) $minutes );

		return sprintf( '%02d:%02d', intdiv( $minutes, 60 ) % 24, $minutes % 60 );
	}

	/**
	 * @param string $source formado|impresion.
	 * @param string $week Y-m-d lunes.
	 * @param int    $day_index 0–6.
	 * @return list<array<string, mixed>>
	 */
	private static function collect_source_jobs( $source, $week, $day_index ) {
		$class = 'formado' === $source
			? Logistica_Forming_Planning::class
			: Logistica_Print_Planning::class;

		$machines = $class::get_machines();
		$jobs     = array();
		$now      = self::now_minutes();

		foreach ( $machines as $machine_slug => $machine_label ) {
			$schedule = $class::get_schedule( $week, $machine_slug );
			$tasks    = is_array( $schedule['tasks'] ?? null ) ? $schedule['tasks'] : array();

			foreach ( $tasks as $task ) {
				if ( ! is_array( $task ) ) {
					continue;
				}
				if ( (int) ( $task['day'] ?? -1 ) !== $day_index ) {
					continue;
				}

				$jobs[] = self::normalize_job_card( $task, $source, $machine_slug, (string) $machine_label, $now );
			}
		}

		return $jobs;
	}

	/**
	 * @param array<string, mixed> $task Tarea.
	 * @param string               $source formado|impresion.
	 * @param string               $machine_slug Slug.
	 * @param string               $machine_label Etiqueta.
	 * @param int                  $now_minutes Ahora.
	 * @return array<string, mixed>
	 */
	private static function normalize_job_card( $task, $source, $machine_slug, $machine_label, $now_minutes ) {
		$pedido_estado = '';
		$line_id       = (int) ( $task['pedido_orden_linea_id'] ?? 0 );
		if ( $line_id > 0 ) {
			global $wpdb;
			$table = Logistica_Database::table( 'pedidos_ordenes_lineas' );
			$estado_raw = $wpdb->get_var(
				$wpdb->prepare( "SELECT estado FROM {$table} WHERE id = %d LIMIT 1", $line_id )
			);
			$pedido_estado = sanitize_key( (string) $estado_raw );
		}

		$start = isset( $task['actual_start'] ) && null !== $task['actual_start'] && '' !== $task['actual_start']
			? (int) $task['actual_start']
			: (int) ( $task['start_minutes'] ?? 0 );
		$planned_start = (int) ( $task['start_minutes'] ?? $start );
		$duration      = max( 0, (int) ( $task['duration_minutes'] ?? 0 ) );
		$end           = $start + $duration;
		$elapsed       = max( 0, $now_minutes - $start );
		$remaining     = $end - $now_minutes;
		$overtime      = $duration > 0 && $now_minutes > $end && 'done' !== sanitize_key( (string) ( $task['status'] ?? '' ) );
		$alert         = self::resolve_alert( $task, $now_minutes );
		$priority      = self::sanitize_priority( $task['board_priority'] ?? 'plan_b' );
		$lane          = self::resolve_lane( $task, $pedido_estado );

		$ref = (string) ( $task['formado_referencia'] ?? '' );
		if ( '' === $ref ) {
			$ref = (string) ( $task['labor'] ?? '' );
		}

		return array(
			'id'                => (string) ( $task['id'] ?? '' ),
			'source'            => $source,
			'machine'           => $machine_slug,
			'machine_label'     => $machine_label,
			'op'                => (string) ( $task['op'] ?? '' ),
			'labor'             => (string) ( $task['labor'] ?? '' ),
			'labor_type'        => (string) ( $task['labor_type'] ?? '' ),
			'referencia'        => $ref,
			'tamanos'           => (string) ( $task['tamanos'] ?? '' ),
			'cliente'           => (string) ( $task['pedido_cliente'] ?? '' ),
			'oc'                => (string) ( $task['pedido_oc_numero'] ?? '' ),
			'pedido_estado'     => $pedido_estado,
			'status'            => (string) ( $task['status'] ?? 'pending' ),
			'lane'              => $lane,
			'assigned_person'   => (string) ( $task['assigned_person'] ?? '' ),
			'board_priority'    => $priority,
			'board_alert'       => $alert,
			'notes'             => (string) ( $task['notes'] ?? '' ),
			'start_minutes'     => $planned_start,
			'actual_start'      => isset( $task['actual_start'] ) ? $task['actual_start'] : null,
			'duration_minutes'  => $duration,
			'timer_start'       => $start,
			'timer_end'         => $end,
			'start_clock'       => self::format_clock( $start ),
			'end_clock'         => self::format_clock( $end ),
			'programmed_label'  => $duration > 0 ? self::format_duration_label( $duration ) : '—',
			'elapsed_minutes'   => $elapsed,
			'remaining_minutes' => $remaining,
			'is_overtime'       => $overtime,
			'sort_order'        => (int) ( $task['sort_order'] ?? 0 ),
		);
	}

	/**
	 * @param int $minutes Minutos.
	 * @return string
	 */
	public static function format_duration_label( $minutes ) {
		$minutes = max( 0, (int) $minutes );
		$h       = intdiv( $minutes, 60 );
		$m       = $minutes % 60;
		if ( $h > 0 && $m > 0 ) {
			return sprintf( '%dh %02dm', $h, $m );
		}
		if ( $h > 0 ) {
			return sprintf( '%dh', $h );
		}

		return sprintf( '%dm', $m );
	}

	/**
	 * Payload para el tablero del día.
	 *
	 * @param string $date   Y-m-d (vacío = hoy).
	 * @param string $etapa  all|clave de etapa (formado, impresion, troquelado…).
	 * @return array<string, mixed>
	 */
	public static function get_board_payload( $date = '', $etapa = 'all' ) {
		$date = preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $date ) ? $date : wp_date( 'Y-m-d' );
		$week = class_exists( 'Logistica_Forming_Planning' )
			? Logistica_Forming_Planning::week_start( $date )
			: $date;
		$day_index = self::day_index_for_date( $date );
		$lanes     = self::get_lanes();
		$etapa     = sanitize_key( (string) $etapa );
		if ( 'all' !== $etapa && ! isset( $lanes[ $etapa ] ) ) {
			$etapa = 'all';
		}

		$jobs = array_merge(
			self::collect_source_jobs( 'formado', $week, $day_index ),
			self::collect_source_jobs( 'impresion', $week, $day_index )
		);

		usort(
			$jobs,
			static function ( $a, $b ) {
				$prio = array( 'plan_a' => 0, 'plan_b' => 1, 'plan_c' => 2 );
				$pa   = $prio[ $a['board_priority'] ] ?? 1;
				$pb   = $prio[ $b['board_priority'] ] ?? 1;
				if ( $pa !== $pb ) {
					return $pa <=> $pb;
				}
				if ( (int) $a['timer_start'] !== (int) $b['timer_start'] ) {
					return (int) $a['timer_start'] <=> (int) $b['timer_start'];
				}

				return (int) $a['sort_order'] <=> (int) $b['sort_order'];
			}
		);

		if ( 'all' !== $etapa ) {
			$jobs = array_values(
				array_filter(
					$jobs,
					static function ( $job ) use ( $etapa ) {
						return ( $job['lane'] ?? '' ) === $etapa;
					}
				)
			);
		}

		$by_lane = array();
		foreach ( array_keys( $lanes ) as $lane ) {
			$by_lane[ $lane ] = array();
		}
		foreach ( $jobs as $job ) {
			$lane = $job['lane'] ?? 'otros';
			if ( ! isset( $by_lane[ $lane ] ) ) {
				$lane = 'otros';
			}
			$by_lane[ $lane ][] = $job;
		}

		return array(
			'date'          => $date,
			'week_start'    => $week,
			'day_index'     => $day_index,
			'now_minutes'   => self::now_minutes(),
			'server_time'   => wp_date( 'c' ),
			'poll_seconds'  => self::POLL_SECONDS,
			'filter'        => $etapa,
			'etapa'         => $etapa,
			'lanes'         => $lanes,
			'priorities'    => self::get_priorities(),
			'alerts'        => self::get_alerts(),
			'jobs'          => $jobs,
			'jobs_by_lane'  => $by_lane,
			'counts'        => array(
				'total'    => count( $jobs ),
				'overtime' => count( array_filter( $jobs, static function ( $j ) {
					return ! empty( $j['is_overtime'] );
				} ) ),
				'plan_a'   => count( array_filter( $jobs, static function ( $j ) {
					return 'plan_a' === ( $j['board_priority'] ?? '' );
				} ) ),
			),
		);
	}

	/**
	 * Actualiza campos del tablero en una tarea de planeación.
	 *
	 * @param string               $source formado|impresion.
	 * @param string               $machine Slug máquina.
	 * @param string               $task_id ID tarea.
	 * @param array<string, mixed> $fields Campos.
	 * @return array<string, mixed>|WP_Error
	 */
	public static function update_job_fields( $source, $machine, $task_id, $fields ) {
		$class = 'formado' === $source
			? Logistica_Forming_Planning::class
			: Logistica_Print_Planning::class;

		$week     = $class::week_start();
		$machine  = $class::sanitize_machine( $machine );
		$task_id  = sanitize_text_field( (string) $task_id );
		if ( '' === $machine || '' === $task_id ) {
			return new WP_Error( 'invalid', __( 'Máquina o tarea no válida.', 'logistica' ) );
		}

		$schedule = $class::get_schedule( $week, $machine );
		$tasks    = is_array( $schedule['tasks'] ?? null ) ? $schedule['tasks'] : array();
		$found    = false;

		foreach ( $tasks as &$task ) {
			if ( ! is_array( $task ) || (string) ( $task['id'] ?? '' ) !== $task_id ) {
				continue;
			}
			$found = true;
			if ( array_key_exists( 'assigned_person', $fields ) ) {
				$task['assigned_person'] = sanitize_text_field( (string) $fields['assigned_person'] );
			}
			if ( array_key_exists( 'board_priority', $fields ) ) {
				$task['board_priority'] = self::sanitize_priority( $fields['board_priority'] );
			}
			if ( array_key_exists( 'board_alert', $fields ) ) {
				$task['board_alert'] = self::sanitize_alert( $fields['board_alert'] );
			}
			if ( array_key_exists( 'status', $fields ) ) {
				$status = sanitize_key( (string) $fields['status'] );
				$allowed = method_exists( $class, 'get_progress_statuses' )
					? $class::get_progress_statuses()
					: $class::get_statuses();
				if ( isset( $allowed[ $status ] ) ) {
					$task['status']        = $status;
					$task['status_manual'] = true;
				}
			}
			break;
		}
		unset( $task );

		if ( ! $found ) {
			// Buscar en otras semanas cercanas no es habitual; reintenta con day match en schedule.
			return new WP_Error( 'not_found', __( 'No se encontró la actividad en la planeación de esta semana.', 'logistica' ) );
		}

		$result = $class::save_schedule(
			$week,
			$machine,
			array(
				'tasks'          => $tasks,
				'day_hours'      => $schedule['day_hours'] ?? null,
				'jornada_inicio' => $schedule['jornada_inicio'] ?? null,
				'jornada_fin'    => $schedule['jornada_fin'] ?? null,
				'lunch_start'    => $schedule['lunch_start'] ?? null,
				'lunch_end'      => $schedule['lunch_end'] ?? null,
			)
		);

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

		$payload = self::get_board_payload( wp_date( 'Y-m-d' ), 'all' );
		foreach ( $payload['jobs'] as $job ) {
			if ( $job['id'] === $task_id && $job['source'] === $source && $job['machine'] === $machine ) {
				return $job;
			}
		}

		return array( 'id' => $task_id, 'saved' => true );
	}
}
