<?php
/**
 * Exportación Excel personalizada para planeación de impresión.
 *
 * @package Logistica
 */

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

/**
 * Genera .xlsx con estilos (colores, tipografía) vía ZipArchive.
 */
class Logistica_Print_Planning_Excel {

	/** @var array<string, int> */
	private static $status_styles = array(
		'pending'     => 5,
		'in_progress' => 6,
		'done'        => 7,
		'delayed'     => 8,
	);

	/**
	 * Descarga la planeación semanal de una máquina.
	 *
	 * @param string $week_start Y-m-d (lunes).
	 * @param string $machine    Slug de máquina.
	 * @return void
	 */
	public static function download( $week_start, $machine ) {
		$week_start = Logistica_Print_Planning::week_start( $week_start );
		$machine    = Logistica_Print_Planning::sanitize_machine( $machine );
		if ( ! $machine ) {
			wp_die( esc_html__( 'Máquina no válida.', 'logistica' ) );
		}

		$schedule      = Logistica_Print_Planning::get_schedule( $week_start, $machine );
		$machines      = Logistica_Print_Planning::get_machines();
		$days          = Logistica_Print_Planning::week_days( $week_start );
		$materials     = Logistica_Print_Planning::get_papel_materials();
		$machine_label = $machines[ $machine ] ?? strtoupper( $machine );

		if ( class_exists( 'ZipArchive', false ) ) {
			self::download_xlsx( $schedule, $days, $machine_label, $materials, $machine, $week_start );
			return;
		}

		self::download_html_excel( $schedule, $days, $machine_label, $materials, $machine, $week_start );
	}

	/**
	 * Importa actividades desde Excel/CSV exportado por este módulo.
	 *
	 * @param array<string, mixed> $file Entrada $_FILES.
	 * @return array{tasks:array<int, array<string, mixed>>,count:int}|\WP_Error
	 */
	public static function import_upload( $file ) {
		return Logistica_Planning_Excel_Import::import_upload(
			$file,
			array(
				'columns'           => self::columns(),
				'day_labels'        => Logistica_Print_Planning::DAYS,
				'statuses'          => Logistica_Print_Planning::STATUSES,
				'labor_types'       => Logistica_Print_Planning::LABOR_TYPES,
				'complexity_labels' => Logistica_Print_Planning::get_complexity_labels(),
				'sanitize_tasks'    => array( Logistica_Print_Planning::class, 'sanitize_tasks' ),
			)
		);
	}

	/**
	 * @param array<string, mixed>             $schedule      Semana.
	 * @param array<int, array<string, mixed>> $days          Días.
	 * @param string                           $machine_label Etiqueta.
	 * @param array<string, string>            $materials     Materiales.
	 * @param string                           $machine       Slug.
	 * @param string                           $week_start    Y-m-d.
	 * @return void
	 */
	private static function download_xlsx( $schedule, $days, $machine_label, $materials, $machine, $week_start ) {
		$sheet_xml = self::build_sheet_xml( $schedule, $days, $machine_label, $materials );
		$filename  = self::build_filename( $machine, $week_start, 'xlsx' );

		$tmp = wp_tempnam( $filename );
		if ( ! $tmp ) {
			wp_die( esc_html__( 'No se pudo crear el archivo temporal.', 'logistica' ) );
		}

		$zip = new ZipArchive();
		if ( true !== $zip->open( $tmp, ZipArchive::OVERWRITE ) ) {
			@unlink( $tmp ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			wp_die( esc_html__( 'No se pudo generar el Excel.', 'logistica' ) );
		}

		$zip->addFromString(
			'[Content_Types].xml',
			'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
			. '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
			. '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
			. '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
			. '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
			. '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'
			. '</Types>'
		);
		$zip->addFromString(
			'_rels/.rels',
			'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
			. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
			. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
			. '</Relationships>'
		);
		$zip->addFromString(
			'xl/workbook.xml',
			'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
			. '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
			. '<sheets><sheet name="' . self::xml_attr( self::sheet_tab_name( $machine_label ) ) . '" sheetId="1" r:id="rId1"/></sheets>'
			. '</workbook>'
		);
		$zip->addFromString(
			'xl/_rels/workbook.xml.rels',
			'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
			. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
			. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
			. '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'
			. '</Relationships>'
		);
		$zip->addFromString( 'xl/styles.xml', self::build_styles_xml() );
		$zip->addFromString( 'xl/worksheets/sheet1.xml', $sheet_xml );
		$zip->close();

		if ( ! is_readable( $tmp ) || filesize( $tmp ) < 1 ) {
			@unlink( $tmp ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			wp_die( esc_html__( 'No se pudo generar el Excel.', 'logistica' ) );
		}

		self::send_download_headers( $filename, filesize( $tmp ) );
		readfile( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
		@unlink( $tmp ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		exit;
	}

	/**
	 * @param string $filename Nombre del archivo.
	 * @param int    $length   Tamaño en bytes.
	 * @return void
	 */
	private static function send_download_headers( $filename, $length ) {
		while ( ob_get_level() ) {
			ob_end_clean();
		}

		nocache_headers();
		header( 'Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		header( 'Content-Length: ' . (string) $length );
	}

	/**
	 * Respaldo compatible con Excel cuando ZipArchive no está disponible (p. ej. XAMPP sin extensión zip).
	 *
	 * @param array<string, mixed>             $schedule      Semana.
	 * @param array<int, array<string, mixed>> $days          Días.
	 * @param string                           $machine_label Etiqueta.
	 * @param array<string, string>            $materials     Materiales.
	 * @param string                           $machine       Slug.
	 * @param string                           $week_start    Y-m-d.
	 * @return void
	 */
	private static function download_html_excel( $schedule, $days, $machine_label, $materials, $machine, $week_start ) {
		$columns = self::columns();
		$colspan = count( $columns );
		$week_end = ! empty( $days ) ? end( $days )['date'] : ( $schedule['semana_inicio'] ?? '' );
		reset( $days );

		$title = sprintf(
			/* translators: 1: machine name 2: week start 3: week end */
			__( 'PLANEACIÓN DE IMPRESIÓN — %1$s — Semana %2$s al %3$s', 'logistica' ),
			$machine_label,
			self::format_date_label( $schedule['semana_inicio'] ?? '' ),
			self::format_date_label( $week_end )
		);
		$hours_line = self::build_hours_summary( $days, $schedule['day_hours'] ?? array() );
		$tasks      = is_array( $schedule['tasks'] ?? null ) ? $schedule['tasks'] : array();
		$by_day     = self::group_tasks_by_day( $tasks );
		$status_bg  = array(
			'pending'     => '#f8fafc',
			'in_progress' => '#ecfdf5',
			'done'        => '#f0fdf4',
			'delayed'     => '#fef2f2',
		);

		$html  = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">';
		$html .= '<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">';
		$html .= '<style>
			body{font-family:Calibri,Arial,sans-serif;font-size:11pt;color:#334155;}
			table{border-collapse:collapse;width:100%;}
			td,th{border:1px solid #cbd5e1;padding:6px 8px;vertical-align:top;}
			.title{background:#0f766e;color:#fff;font-size:16pt;font-weight:bold;text-align:center;}
			.subtitle{background:#ecfdf5;color:#0f766e;font-size:10pt;}
			.header{background:#0b5e58;color:#fff;font-weight:bold;text-align:center;font-size:10pt;}
			.day{background:#ccfbf1;color:#0b5e58;font-weight:bold;}
		</style></head><body><table>';
		$html .= '<tr><td class="title" colspan="' . (int) $colspan . '">' . esc_html( $title ) . '</td></tr>';
		$html .= '<tr><td class="subtitle" colspan="' . (int) $colspan . '">' . esc_html( $hours_line ) . '</td></tr>';
		$html .= '<tr>';
		foreach ( $columns as $label ) {
			$html .= '<th class="header">' . esc_html( $label ) . '</th>';
		}
		$html .= '</tr>';

		foreach ( $days as $day ) {
			$index     = (int) ( $day['index'] ?? 0 );
			$day_label = trim( (string) ( $day['label'] ?? '' ) . ' ' . (string) ( $day['short'] ?? '' ) );
			$html     .= '<tr><td class="day" colspan="' . (int) $colspan . '">' . esc_html( $day_label ) . '</td></tr>';

			$day_tasks = $by_day[ $index ] ?? array();
			if ( empty( $day_tasks ) ) {
				$html .= '<tr style="background:#f8fafc"><td colspan="' . (int) $colspan . '">' . esc_html__( 'Sin actividades', 'logistica' ) . '</td></tr>';
				continue;
			}

			foreach ( $day_tasks as $task ) {
				$status = (string) ( $task['status'] ?? 'pending' );
				$bg     = $status_bg[ $status ] ?? '#ffffff';
				$cells  = self::task_to_cells( $task, $day, $materials );
				$html  .= '<tr style="background:' . esc_attr( $bg ) . '">';
				foreach ( $cells as $cell ) {
					$html .= '<td>' . esc_html( (string) $cell ) . '</td>';
				}
				$html .= '</tr>';
			}
		}

		$html .= '</table></body></html>';

		$filename = self::build_filename( $machine, $week_start, 'xls' );

		nocache_headers();
		header( 'Content-Type: application/vnd.ms-excel; charset=UTF-8' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- BOM + HTML export document.
		echo "\xEF\xBB\xBF" . $html;
		exit;
	}

	/**
	 * @param string $machine Slug.
	 * @param string $week    Y-m-d.
	 * @param string $ext     xlsx|xls.
	 * @return string
	 */
	private static function build_filename( $machine, $week, $ext = 'xlsx' ) {
		$slug = sanitize_file_name( $machine );
		$date = sanitize_file_name( $week );
		$ext  = 'xls' === $ext ? 'xls' : 'xlsx';

		return 'planeacion-impresion-' . $slug . '-' . $date . '.' . $ext;
	}

	/**
	 * @param string $machine_label Etiqueta visible.
	 * @return string
	 */
	private static function sheet_tab_name( $machine_label ) {
		$tab = mb_substr( (string) $machine_label, 0, 31 );
		return '' !== $tab ? $tab : 'Planeacion';
	}

	/**
	 * @param array<string, mixed>             $schedule Datos semana.
	 * @param array<int, array<string, mixed>> $days     Días.
	 * @param string                           $machine_label Nombre máquina.
	 * @param array<string, string>            $materials Mapa materiales.
	 * @return string
	 */
	private static function build_sheet_xml( $schedule, $days, $machine_label, $materials ) {
		$columns = self::columns();
		$col_keys = array_keys( $columns );
		$last_col = self::column_letter( count( $col_keys ) - 1 );

		$week_end = ! empty( $days ) ? end( $days )['date'] : ( $schedule['semana_inicio'] ?? '' );
		reset( $days );

		$title = sprintf(
			/* translators: 1: machine name 2: week start 3: week end */
			__( 'PLANEACIÓN DE IMPRESIÓN — %1$s — Semana %2$s al %3$s', 'logistica' ),
			$machine_label,
			self::format_date_label( $schedule['semana_inicio'] ?? '' ),
			self::format_date_label( $week_end )
		);

		$hours_line = self::build_hours_summary( $days, $schedule['day_hours'] ?? array() );

		$rows   = array();
		$rows[] = self::build_row_xml( 1, array( $title ), array( 1 ), $col_keys );
		$rows[] = self::build_row_xml( 2, array( $hours_line ), array( 2 ), $col_keys );
		$rows[] = self::build_row_xml( 3, array_values( $columns ), array_fill( 0, count( $columns ), 3 ), $col_keys );

		$row_number = 4;
		$tasks      = is_array( $schedule['tasks'] ?? null ) ? $schedule['tasks'] : array();
		$by_day     = self::group_tasks_by_day( $tasks );

		foreach ( $days as $day ) {
			$index     = (int) ( $day['index'] ?? 0 );
			$day_label = trim( (string) ( $day['label'] ?? '' ) . ' ' . (string) ( $day['short'] ?? '' ) );
			$rows[]    = self::build_row_xml(
				$row_number,
				array( $day_label ),
				array( 4 ),
				$col_keys
			);
			++$row_number;

			$day_tasks = $by_day[ $index ] ?? array();
			if ( empty( $day_tasks ) ) {
				$rows[] = self::build_row_xml(
					$row_number,
					array( __( 'Sin actividades', 'logistica' ) ),
					array( 5 ),
					$col_keys
				);
				++$row_number;
				continue;
			}

			foreach ( $day_tasks as $task ) {
				$status = (string) ( $task['status'] ?? 'pending' );
				$style  = self::$status_styles[ $status ] ?? 5;
				$rows[] = self::build_row_xml(
					$row_number,
					self::task_to_cells( $task, $day, $materials ),
					array_fill( 0, count( $col_keys ), $style ),
					$col_keys
				);
				++$row_number;
			}
		}

		$merge = '<mergeCells count="2">'
			. '<mergeCell ref="A1:' . $last_col . '1"/>'
			. '<mergeCell ref="A2:' . $last_col . '2"/>'
			. '</mergeCells>';

		$cols = self::build_column_widths_xml( count( $col_keys ) );

		return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
			. '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
			. '<sheetViews><sheetView workbookViewId="0">'
			. '<pane ySplit="3" topLeftCell="A4" activePane="bottomLeft" state="frozen"/>'
			. '<selection pane="bottomLeft" activeCell="A4" sqref="A4"/>'
			. '</sheetView></sheetViews>'
			. $cols
			. '<sheetData>' . implode( '', $rows ) . '</sheetData>'
			. $merge
			. '</worksheet>';
	}

	/**
	 * @return array<string, string>
	 */
	private static function columns() {
		return array(
			'hora_inicio'   => __( 'Inicio plan.', 'logistica' ),
			'hora_fin'      => __( 'Fin plan.', 'logistica' ),
			'op'            => __( 'OP', 'logistica' ),
			'estado'        => __( 'Estado', 'logistica' ),
			'labor_type'    => __( 'Tipo labor', 'logistica' ),
			'labor'         => __( 'Descripción', 'logistica' ),
			'tamanos'       => __( 'Tamaños', 'logistica' ),
			'duracion'      => __( 'Duración (min)', 'logistica' ),
			'complexity'    => __( 'Complejidad', 'logistica' ),
			'tintas'        => __( 'Tintas', 'logistica' ),
			'colores'       => __( 'Colores', 'logistica' ),
			'papel'         => __( 'Papel', 'logistica' ),
			'material'      => __( 'Material', 'logistica' ),
			'montaje'       => __( 'Montaje', 'logistica' ),
			'prep_tinta'    => __( 'Preparación de tintas', 'logistica' ),
			'planchas'      => __( 'Planchas', 'logistica' ),
			'meta'          => __( 'Cumplimiento de metas', 'logistica' ),
			'inicio_real'   => __( 'Inicio real', 'logistica' ),
			'fin_real'      => __( 'Fin real', 'logistica' ),
			'notas'         => __( 'Notas', 'logistica' ),
		);
	}

	/**
	 * @param array<int, array<string, mixed>> $tasks Tareas.
	 * @return array<int, list<array<string, mixed>>>
	 */
	private static function group_tasks_by_day( $tasks ) {
		$grouped = array();

		foreach ( $tasks as $task ) {
			if ( ! is_array( $task ) ) {
				continue;
			}
			$day = (int) ( $task['day'] ?? 0 );
			if ( ! isset( $grouped[ $day ] ) ) {
				$grouped[ $day ] = array();
			}
			$grouped[ $day ][] = $task;
		}

		foreach ( $grouped as &$day_tasks ) {
			usort(
				$day_tasks,
				static function ( $a, $b ) {
					$sort_a = (int) ( $a['sort_order'] ?? 0 );
					$sort_b = (int) ( $b['sort_order'] ?? 0 );
					if ( $sort_a !== $sort_b ) {
						return $sort_a <=> $sort_b;
					}

					return (int) ( $a['start_minutes'] ?? 0 ) <=> (int) ( $b['start_minutes'] ?? 0 );
				}
			);
		}
		unset( $day_tasks );

		return $grouped;
	}

	/**
	 * @param array<string, mixed>  $task      Tarea.
	 * @param array<string, mixed>  $day       Día.
	 * @param array<string, string> $materials Materiales.
	 * @return list<string>
	 */
	private static function task_to_cells( $task, $day, $materials ) {
		$start = (int) ( $task['start_minutes'] ?? 420 );
		$dur   = (int) ( $task['duration_minutes'] ?? 60 );
		$end   = $start + max( 0, $dur );

		$material_key = (string) ( $task['papel_material'] ?? '' );
		$material     = $materials[ $material_key ] ?? $material_key;
		if ( 'otro' === $material_key && ! empty( $task['papel_material_otro'] ) ) {
			$material = (string) $task['papel_material_otro'];
		}

		$labor_type = (string) ( $task['labor_type'] ?? 'impresion' );
		$status     = (string) ( $task['status'] ?? 'pending' );
		$complexity = (string) ( $task['complexity'] ?? 'normal' );

		return array(
			Logistica_Print_Planning::minutes_to_time_input( $start ),
			Logistica_Print_Planning::minutes_to_time_input( $end ),
			(string) ( $task['op'] ?? '' ),
			Logistica_Print_Planning::STATUSES[ $status ] ?? $status,
			Logistica_Print_Planning::LABOR_TYPES[ $labor_type ] ?? $labor_type,
			(string) ( $task['labor'] ?? '' ),
			(string) ( $task['tamanos'] ?? '' ),
			(string) $dur,
			Logistica_Print_Planning::get_complexity_labels()[ $complexity ] ?? $complexity,
			(string) ( $task['tintas'] ?? '' ),
			(string) ( $task['colores'] ?? '' ),
			(string) ( $task['papel'] ?? '' ),
			$material,
			(string) ( $task['montaje'] ?? '' ),
			(string) ( $task['prep_tinta'] ?? '' ),
			(string) ( $task['planchas'] ?? '' ),
			(string) ( $task['meta'] ?? '' ),
			self::format_optional_time( $task['actual_start'] ?? null ),
			self::format_optional_time( $task['actual_end'] ?? null ),
			(string) ( $task['notes'] ?? '' ),
		);
	}

	/**
	 * @param mixed $minutes Minutos o null.
	 * @return string
	 */
	private static function format_optional_time( $minutes ) {
		if ( null === $minutes || '' === $minutes ) {
			return '';
		}

		return Logistica_Print_Planning::minutes_to_time_input( (int) $minutes );
	}

	/**
	 * @param array<int, array<string, mixed>> $days       Días.
	 * @param array<string, mixed>             $day_hours  Horarios.
	 * @return string
	 */
	private static function build_hours_summary( $days, $day_hours ) {
		$parts = array();

		foreach ( $days as $day ) {
			$key   = (string) ( $day['index'] ?? '' );
			$hours = is_array( $day_hours[ $key ] ?? null ) ? $day_hours[ $key ] : array();
			$start = Logistica_Print_Planning::minutes_to_time_input( (int) ( $hours['start'] ?? 420 ) );
			$end   = Logistica_Print_Planning::minutes_to_time_input( (int) ( $hours['end'] ?? 1020 ) );
			$parts[] = (string) ( $day['label'] ?? '' ) . ' ' . $start . '-' . $end;
		}

		return implode( '  |  ', $parts );
	}

	/**
	 * @param string $date Y-m-d.
	 * @return string
	 */
	private static function format_date_label( $date ) {
		$ts = strtotime( (string) $date );
		if ( false === $ts ) {
			return (string) $date;
		}

		return wp_date( 'd/m/Y', $ts );
	}

	/**
	 * @param int $count Columnas.
	 * @return string
	 */
	private static function build_column_widths_xml( $count ) {
		$widths = array( 11, 11, 10, 14, 18, 24, 12, 12, 12, 10, 14, 22, 12, 12, 16, 12, 16, 11, 11, 28 );
		$xml    = '<cols>';

		for ( $i = 0; $i < $count; $i++ ) {
			$width = $widths[ $i ] ?? 14;
			$xml  .= '<col min="' . ( $i + 1 ) . '" max="' . ( $i + 1 ) . '" width="' . $width . '" customWidth="1"/>';
		}

		$xml .= '</cols>';
		return $xml;
	}

	/**
	 * @param int               $row_number Fila 1-based.
	 * @param list<string>      $cells      Valores (solo primeras celdas si es fila título).
	 * @param list<int>         $styles     Índice de estilo por celda.
	 * @param list<string>      $col_keys   Claves columna.
	 * @return string
	 */
	private static function build_row_xml( $row_number, $cells, $styles, $col_keys ) {
		$height = 1 === $row_number ? ' height="28"' : ( 4 === (int) ( $styles[0] ?? 0 ) ? ' height="22"' : '' );
		$xml    = '<row r="' . (int) $row_number . '"' . $height . '>';
		$cols   = count( $col_keys );

		for ( $index = 0; $index < $cols; $index++ ) {
			$value  = $cells[ $index ] ?? ( 0 === $index ? ( $cells[0] ?? '' ) : '' );
			$style  = (int) ( $styles[ $index ] ?? ( $styles[0] ?? 0 ) );
			$ref    = self::column_letter( $index ) . (int) $row_number;
			$xml   .= self::build_cell_xml( $ref, $value, $style );
		}

		$xml .= '</row>';
		return $xml;
	}

	/**
	 * @param string $ref   Referencia celda.
	 * @param mixed  $value Valor.
	 * @param int    $style Índice estilo.
	 * @return string
	 */
	private static function build_cell_xml( $ref, $value, $style ) {
		$style_attr = ' s="' . (int) $style . '"';

		if ( is_numeric( $value ) && '' !== $value ) {
			return '<c r="' . $ref . '"' . $style_attr . '><v>' . self::xml_escape( (string) $value ) . '</v></c>';
		}

		return '<c r="' . $ref . '"' . $style_attr . ' t="inlineStr"><is><t xml:space="preserve">' . self::xml_escape( (string) $value ) . '</t></is></c>';
	}

	/**
	 * @return string
	 */
	private static function build_styles_xml() {
		return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
			. '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
			. '<fonts count="6">'
			. '<font><sz val="11"/><color rgb="FF334155"/><name val="Calibri"/><family val="2"/></font>'
			. '<font><b/><sz val="16"/><color rgb="FFFFFFFF"/><name val="Calibri"/><family val="2"/></font>'
			. '<font><sz val="10"/><color rgb="FF0F766E"/><name val="Calibri"/><family val="2"/></font>'
			. '<font><b/><sz val="11"/><color rgb="FFFFFFFF"/><name val="Calibri"/><family val="2"/></font>'
			. '<font><b/><sz val="11"/><color rgb="FF0B5E58"/><name val="Calibri"/><family val="2"/></font>'
			. '<font><b/><sz val="11"/><color rgb="FF334155"/><name val="Calibri"/><family val="2"/></font>'
			. '</fonts>'
			. '<fills count="12">'
			. '<fill><patternFill patternType="none"/></fill>'
			. '<fill><patternFill patternType="gray125"/></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FF0F766E"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFECFDF5"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FF0B5E58"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFCCFBF1"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFF8FAFC"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFECFDF5"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFF0FDF4"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFFEF2F2"/><bgColor indexed="64"/></patternFill></fill>'
			. '<fill><patternFill patternType="solid"><fgColor rgb="FFFFFFFF"/><bgColor indexed="64"/></patternFill></fill>'
			. '</fills>'
			. '<borders count="2">'
			. '<border><left/><right/><top/><bottom/><diagonal/></border>'
			. '<border>'
			. '<left style="thin"><color rgb="FFCBD5E1"/></left>'
			. '<right style="thin"><color rgb="FFCBD5E1"/></right>'
			. '<top style="thin"><color rgb="FFCBD5E1"/></top>'
			. '<bottom style="thin"><color rgb="FFCBD5E1"/></bottom>'
			. '<diagonal/>'
			. '</border>'
			. '</borders>'
			. '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>'
			. '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>'
			. '<cellXfs count="9">'
			. '<xf numFmtId="0" fontId="0" fillId="10" borderId="1" xfId="0" applyFill="1" applyBorder="1"/>'
			. '<xf numFmtId="0" fontId="1" fillId="2" borderId="0" xfId="0" applyFont="1" applyFill="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>'
			. '<xf numFmtId="0" fontId="2" fillId="3" borderId="0" xfId="0" applyFont="1" applyFill="1" applyAlignment="1"><alignment horizontal="left" vertical="center" wrapText="1"/></xf>'
			. '<xf numFmtId="0" fontId="3" fillId="4" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>'
			. '<xf numFmtId="0" fontId="4" fillId="5" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="center"/></xf>'
			. '<xf numFmtId="0" fontId="0" fillId="6" borderId="1" xfId="0" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>'
			. '<xf numFmtId="0" fontId="0" fillId="7" borderId="1" xfId="0" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>'
			. '<xf numFmtId="0" fontId="0" fillId="8" borderId="1" xfId="0" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>'
			. '<xf numFmtId="0" fontId="0" fillId="9" borderId="1" xfId="0" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>'
			. '</cellXfs>'
			. '</styleSheet>';
	}

	/**
	 * @param int $index Índice base 0.
	 * @return string
	 */
	private static function column_letter( $index ) {
		$index  = max( 0, (int) $index );
		$letter = '';

		do {
			$letter = chr( 65 + ( $index % 26 ) ) . $letter;
			$index  = (int) floor( $index / 26 ) - 1;
		} while ( $index >= 0 );

		return $letter;
	}

	/**
	 * @param string $value Texto.
	 * @return string
	 */
	private static function xml_escape( $value ) {
		return htmlspecialchars( (string) $value, ENT_XML1 | ENT_QUOTES, 'UTF-8' );
	}

	/**
	 * @param string $value Atributo.
	 * @return string
	 */
	private static function xml_attr( $value ) {
		return self::xml_escape( $value );
	}
}
