<?php

namespace TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx;

use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\Axis;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\AxisText;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\GridLines;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\Layout;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\Legend;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\Properties as ChartProperties;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\Title;
use TablePress\PhpOffice\PhpSpreadsheet\Chart\TrendLine;
use TablePress\PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText;
use TablePress\PhpOffice\PhpSpreadsheet\Style\Font;
use SimpleXMLElement;

class Chart
{
	private string $cNamespace;

	private string $aNamespace;

	public function __construct(string $cNamespace = Namespaces::CHART, string $aNamespace = Namespaces::DRAWINGML)
	{
		$this->cNamespace = $cNamespace;
		$this->aNamespace = $aNamespace;
	}

	private static function getAttributeString(SimpleXMLElement $component, string $name): ?string
	{
		$attributes = $component->attributes();
		if (@isset($attributes[$name])) {
			return (string) $attributes[$name];
		}

		return null;
	}

	private static function getAttributeInteger(SimpleXMLElement $component, string $name): ?int
	{
		$attributes = $component->attributes();
		if (@isset($attributes[$name])) {
			return (int) $attributes[$name];
		}

		return null;
	}

	private static function getAttributeBoolean(SimpleXMLElement $component, string $name): ?bool
	{
		$attributes = $component->attributes();
		if (@isset($attributes[$name])) {
			$value = (string) $attributes[$name];

			return $value === 'true' || $value === '1';
		}

		return null;
	}

	private static function getAttributeFloat(SimpleXMLElement $component, string $name): ?float
	{
		$attributes = $component->attributes();
		if (@isset($attributes[$name])) {
			return (float) $attributes[$name];
		}

		return null;
	}

	public function readChart(SimpleXMLElement $chartElements, string $chartName): \TablePress\PhpOffice\PhpSpreadsheet\Chart\Chart
	{
		$chartElementsC = $chartElements->children($this->cNamespace);

		$XaxisLabel = $YaxisLabel = $legend = $title = null;
		$dispBlanksAs = null;
		$plotVisOnly = false;
		$plotArea = null;
		$rotX = $rotY = $rAngAx = $perspective = null;
		$xAxis = new Axis();
		$yAxis = new Axis();
		$autoTitleDeleted = null;
		$chartNoFill = false;
		$chartBorderLines = null;
		$chartFillColor = null;
		$gradientArray = [];
		$gradientLin = null;
		$roundedCorners = false;
		$gapWidth = null;
		$useUpBars = null;
		$useDownBars = null;
		$noBorder = false;
		foreach ($chartElementsC as $chartElementKey => $chartElement) {
			switch ($chartElementKey) {
				case 'spPr':
					$children = $chartElementsC->spPr->children($this->aNamespace);
					if (isset($children->noFill)) {
						$chartNoFill = true;
					}
					if (isset($children->solidFill)) {
						$chartFillColor = $this->readColor($children->solidFill);
					}
					if (isset($children->ln)) {
						$chartBorderLines = new GridLines();
						$this->readLineStyle($chartElementsC, $chartBorderLines);
						if (isset($children->ln->noFill)) {
							$noBorder = true;
						}
					}

					break;
				case 'roundedCorners':
					/** @var bool $roundedCorners */
					$roundedCorners = self::getAttributeBoolean($chartElementsC->roundedCorners, 'val');

					break;
				case 'chart':
					foreach ($chartElement as $chartDetailsKey => $chartDetails) {
						$chartDetails = Xlsx::testSimpleXml($chartDetails);
						switch ($chartDetailsKey) {
							case 'autoTitleDeleted':
								/** @var bool $autoTitleDeleted */
								$autoTitleDeleted = self::getAttributeBoolean($chartElementsC->chart->autoTitleDeleted, 'val');

								break;
							case 'view3D':
								$rotX = self::getAttributeInteger($chartDetails->rotX, 'val');
								$rotY = self::getAttributeInteger($chartDetails->rotY, 'val');
								$rAngAx = self::getAttributeInteger($chartDetails->rAngAx, 'val');
								$perspective = self::getAttributeInteger($chartDetails->perspective, 'val');

								break;
							case 'plotArea':
								$plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
								$plotSeries = $plotAttributes = [];
								$catAxRead = false;
								$plotNoFill = false;
								foreach ($chartDetails as $chartDetailKey => $chartDetail) {
									$chartDetail = Xlsx::testSimpleXml($chartDetail);
									switch ($chartDetailKey) {
										case 'spPr':
											$possibleNoFill = $chartDetails->spPr->children($this->aNamespace);
											if (isset($possibleNoFill->noFill)) {
												$plotNoFill = true;
											}
											if (isset($possibleNoFill->gradFill->gsLst)) {
												foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) {
													$gradient = Xlsx::testSimpleXml($gradient);
													/** @var float $pos */
													$pos = self::getAttributeFloat($gradient, 'pos');
													$gradientArray[] = [
														$pos / ChartProperties::PERCENTAGE_MULTIPLIER,
														new ChartColor($this->readColor($gradient)),
													];
												}
											}
											if (isset($possibleNoFill->gradFill->lin)) {
												$gradientLin = ChartProperties::XmlToAngle((string) self::getAttributeString($possibleNoFill->gradFill->lin, 'ang'));
											}

											break;
										case 'layout':
											$plotAreaLayout = $this->chartLayoutDetails($chartDetail);

											break;
										case Axis::AXIS_TYPE_CATEGORY:
										case Axis::AXIS_TYPE_DATE:
											$catAxRead = true;
											if (isset($chartDetail->title)) {
												$XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
											}
											$xAxis->setAxisType($chartDetailKey);
											$this->readEffects($chartDetail, $xAxis);
											$this->readLineStyle($chartDetail, $xAxis);
											if (isset($chartDetail->spPr)) {
												$sppr = $chartDetail->spPr->children($this->aNamespace);
												if (isset($sppr->solidFill)) {
													$axisColorArray = $this->readColor($sppr->solidFill);
													$xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
												}
												if (isset($chartDetail->spPr->ln->noFill)) {
													$xAxis->setNoFill(true);
												}
											}
											if (isset($chartDetail->majorGridlines)) {
												$majorGridlines = new GridLines();
												if (isset($chartDetail->majorGridlines->spPr)) {
													$this->readEffects($chartDetail->majorGridlines, $majorGridlines);
													$this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
												}
												$xAxis->setMajorGridlines($majorGridlines);
											}
											if (isset($chartDetail->minorGridlines)) {
												$minorGridlines = new GridLines();
												$minorGridlines->activateObject();
												if (isset($chartDetail->minorGridlines->spPr)) {
													$this->readEffects($chartDetail->minorGridlines, $minorGridlines);
													$this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
												}
												$xAxis->setMinorGridlines($minorGridlines);
											}
											$this->setAxisProperties($chartDetail, $xAxis);

											break;
										case Axis::AXIS_TYPE_VALUE:
											$whichAxis = null;
											$axPos = null;
											if (isset($chartDetail->axPos)) {
												$axPos = self::getAttributeString($chartDetail->axPos, 'val');
											}
											if ($catAxRead) {
												$whichAxis = $yAxis;
												$yAxis->setAxisType($chartDetailKey);
											} elseif (!empty($axPos)) {
												switch ($axPos) {
													case 't':
													case 'b':
														$whichAxis = $xAxis;
														$xAxis->setAxisType($chartDetailKey);

														break;
													case 'r':
													case 'l':
														$whichAxis = $yAxis;
														$yAxis->setAxisType($chartDetailKey);

														break;
												}
											}
											if (isset($chartDetail->title)) {
												$axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));

												switch ($axPos) {
													case 't':
													case 'b':
														$XaxisLabel = $axisLabel;

														break;
													case 'r':
													case 'l':
														$YaxisLabel = $axisLabel;

														break;
												}
											}
											$this->readEffects($chartDetail, $whichAxis);
											$this->readLineStyle($chartDetail, $whichAxis);
											if ($whichAxis !== null && isset($chartDetail->spPr)) {
												$sppr = $chartDetail->spPr->children($this->aNamespace);
												if (isset($sppr->solidFill)) {
													$axisColorArray = $this->readColor($sppr->solidFill);
													$whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
												}
												if (isset($sppr->ln->noFill)) {
													$whichAxis->setNoFill(true);
												}
											}
											if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
												$majorGridlines = new GridLines();
												if (isset($chartDetail->majorGridlines->spPr)) {
													$this->readEffects($chartDetail->majorGridlines, $majorGridlines);
													$this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
												}
												$whichAxis->setMajorGridlines($majorGridlines);
											}
											if ($whichAxis !== null && isset($chartDetail->minorGridlines)) {
												$minorGridlines = new GridLines();
												$minorGridlines->activateObject();
												if (isset($chartDetail->minorGridlines->spPr)) {
													$this->readEffects($chartDetail->minorGridlines, $minorGridlines);
													$this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
												}
												$whichAxis->setMinorGridlines($minorGridlines);
											}
											$this->setAxisProperties($chartDetail, $whichAxis);

											break;
										case 'barChart':
										case 'bar3DChart':
											$barDirection = self::getAttributeString($chartDetail->barDir, 'val');
											$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotSer->setPlotDirection("$barDirection");
											$plotSeries[] = $plotSer;
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'lineChart':
										case 'line3DChart':
											$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'areaChart':
										case 'area3DChart':
											$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'doughnutChart':
										case 'pieChart':
										case 'pie3DChart':
											$explosion = self::getAttributeString($chartDetail->ser->explosion, 'val');
											$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotSer->setPlotStyle("$explosion");
											$plotSeries[] = $plotSer;
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'scatterChart':
											/** @var string $scatterStyle */
											$scatterStyle = self::getAttributeString($chartDetail->scatterStyle, 'val');
											$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotSer->setPlotStyle($scatterStyle);
											$plotSeries[] = $plotSer;
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'bubbleChart':
											$bubbleScale = self::getAttributeInteger($chartDetail->bubbleScale, 'val');
											$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotSer->setPlotStyle("$bubbleScale");
											$plotSeries[] = $plotSer;
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'radarChart':
											/** @var string $radarStyle */
											$radarStyle = self::getAttributeString($chartDetail->radarStyle, 'val');
											$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotSer->setPlotStyle($radarStyle);
											$plotSeries[] = $plotSer;
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'surfaceChart':
										case 'surface3DChart':
											$wireFrame = self::getAttributeBoolean($chartDetail->wireframe, 'val');
											$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
											$plotSer->setPlotStyle("$wireFrame");
											$plotSeries[] = $plotSer;
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
										case 'stockChart':
											$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
											if (isset($chartDetail->upDownBars->gapWidth)) {
												$gapWidth = self::getAttributeInteger($chartDetail->upDownBars->gapWidth, 'val');
											}
											if (isset($chartDetail->upDownBars->upBars)) {
												$useUpBars = true;
											}
											if (isset($chartDetail->upDownBars->downBars)) {
												$useDownBars = true;
											}
											$plotAttributes = $this->readChartAttributes($chartDetail);

											break;
									}
								}
								if ($plotAreaLayout == null) {
									$plotAreaLayout = new Layout();
								}
								$plotArea = new PlotArea($plotAreaLayout, $plotSeries);
								$this->setChartAttributes($plotAreaLayout, $plotAttributes);
								if ($plotNoFill) {
									$plotArea->setNoFill(true);
								}
								if (!empty($gradientArray)) {
									$plotArea->setGradientFillProperties($gradientArray, $gradientLin);
								}
								if (is_int($gapWidth)) {
									$plotArea->setGapWidth($gapWidth);
								}
								if ($useUpBars === true) {
									$plotArea->setUseUpBars(true);
								}
								if ($useDownBars === true) {
									$plotArea->setUseDownBars(true);
								}

								break;
							case 'plotVisOnly':
								$plotVisOnly = (bool) self::getAttributeString($chartDetails, 'val');

								break;
							case 'dispBlanksAs':
								$dispBlanksAs = self::getAttributeString($chartDetails, 'val');

								break;
							case 'title':
								$title = $this->chartTitle($chartDetails);

								break;
							case 'legend':
								$legendPos = 'r';
								$legendLayout = null;
								$legendOverlay = false;
								$legendBorderLines = null;
								$legendFillColor = null;
								$legendText = null;
								$addLegendText = false;
								foreach ($chartDetails as $chartDetailKey => $chartDetail) {
									$chartDetail = Xlsx::testSimpleXml($chartDetail);
									switch ($chartDetailKey) {
										case 'legendPos':
											$legendPos = self::getAttributeString($chartDetail, 'val');

											break;
										case 'overlay':
											$legendOverlay = self::getAttributeBoolean($chartDetail, 'val');

											break;
										case 'layout':
											$legendLayout = $this->chartLayoutDetails($chartDetail);

											break;
										case 'spPr':
											$children = $chartDetails->spPr->children($this->aNamespace);
											if (isset($children->solidFill)) {
												$legendFillColor = $this->readColor($children->solidFill);
											}
											if (isset($children->ln)) {
												$legendBorderLines = new GridLines();
												$this->readLineStyle($chartDetails, $legendBorderLines);
											}

											break;
										case 'txPr':
											$children = $chartDetails->txPr->children($this->aNamespace);
											$addLegendText = false;
											$legendText = new AxisText();
											if (isset($children->p->pPr->defRPr->solidFill)) {
												$colorArray = $this->readColor($children->p->pPr->defRPr->solidFill);
												$legendText->getFillColorObject()->setColorPropertiesArray($colorArray);
												$addLegendText = true;
											}
											if (isset($children->p->pPr->defRPr->effectLst)) {
												$this->readEffects($children->p->pPr->defRPr, $legendText, false);
												$addLegendText = true;
											}

											break;
									}
								}
								$legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
								if ($legendFillColor !== null) {
									$legend->getFillColor()->setColorPropertiesArray($legendFillColor);
								}
								if ($legendBorderLines !== null) {
									$legend->setBorderLines($legendBorderLines);
								}
								if ($addLegendText) {
									$legend->setLegendText($legendText);
								}

								break;
						}
					}
			}
		}
		$chart = new \TablePress\PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
		if ($chartNoFill) {
			$chart->setNoFill(true);
		}
		if ($chartFillColor !== null) {
			$chart->getFillColor()->setColorPropertiesArray($chartFillColor);
		}
		if ($chartBorderLines !== null) {
			$chart->setBorderLines($chartBorderLines);
		}
		$chart->setNoBorder($noBorder);
		$chart->setRoundedCorners($roundedCorners);
		if (is_bool($autoTitleDeleted)) {
			$chart->setAutoTitleDeleted($autoTitleDeleted);
		}
		if (is_int($rotX)) {
			$chart->setRotX($rotX);
		}
		if (is_int($rotY)) {
			$chart->setRotY($rotY);
		}
		if (is_int($rAngAx)) {
			$chart->setRAngAx($rAngAx);
		}
		if (is_int($perspective)) {
			$chart->setPerspective($perspective);
		}

		return $chart;
	}

	private function chartTitle(SimpleXMLElement $titleDetails): Title
	{
		$caption = '';
		$titleLayout = null;
		$titleOverlay = false;
		$titleFormula = null;
		$titleFont = null;
		foreach ($titleDetails as $titleDetailKey => $chartDetail) {
			$chartDetail = Xlsx::testSimpleXml($chartDetail);
			switch ($titleDetailKey) {
				case 'tx':
					$caption = [];
					if (isset($chartDetail->rich)) {
						$titleDetails = $chartDetail->rich->children($this->aNamespace);
						foreach ($titleDetails as $titleKey => $titleDetail) {
							$titleDetail = Xlsx::testSimpleXml($titleDetail);
							switch ($titleKey) {
								case 'p':
									$titleDetailPart = $titleDetail->children($this->aNamespace);
									$caption[] = $this->parseRichText($titleDetailPart);
							}
						}
					} elseif (isset($chartDetail->strRef->strCache)) {
						foreach ($chartDetail->strRef->strCache->pt as $pt) {
							if (isset($pt->v)) {
								$caption[] = (string) $pt->v;
							}
						}
						if (isset($chartDetail->strRef->f)) {
							$titleFormula = (string) $chartDetail->strRef->f;
						}
					}

					break;
				case 'overlay':
					$titleOverlay = self::getAttributeBoolean($chartDetail, 'val');

					break;
				case 'layout':
					$titleLayout = $this->chartLayoutDetails($chartDetail);

					break;
				case 'txPr':
					if (isset($chartDetail->children($this->aNamespace)->p)) {
						$titleFont = $this->parseFont($chartDetail->children($this->aNamespace)->p);
					}

					break;
			}
		}
		$title = new Title($caption, $titleLayout, (bool) $titleOverlay);
		if (!empty($titleFormula)) {
			$title->setCellReference($titleFormula);
		}
		if ($titleFont !== null) {
			$title->setFont($titleFont);
		}

		return $title;
	}

	private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout
	{
		if (!isset($chartDetail->manualLayout)) {
			return null;
		}
		$details = $chartDetail->manualLayout->children($this->cNamespace);
		if ($details === null) {
			return null;
		}
		$layout = [];
		foreach ($details as $detailKey => $detail) {
			$detail = Xlsx::testSimpleXml($detail);
			$layout[$detailKey] = self::getAttributeString($detail, 'val');
		}

		return new Layout($layout);
	}

	private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries
	{
		$multiSeriesType = null;
		$smoothLine = false;
		$seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
		$plotDirection = null;

		$seriesDetailSet = $chartDetail->children($this->cNamespace);
		foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
			switch ($seriesDetailKey) {
				case 'grouping':
					$multiSeriesType = self::getAttributeString($chartDetail->grouping, 'val');

					break;
				case 'ser':
					$marker = null;
					$seriesIndex = '';
					$fillColor = null;
					$pointSize = null;
					$noFill = false;
					$bubble3D = false;
					$dptColors = [];
					$markerFillColor = null;
					$markerBorderColor = null;
					$lineStyle = null;
					$labelLayout = null;
					$trendLines = [];
					foreach ($seriesDetails as $seriesKey => $seriesDetail) {
						$seriesDetail = Xlsx::testSimpleXml($seriesDetail);
						switch ($seriesKey) {
							case 'idx':
								$seriesIndex = self::getAttributeInteger($seriesDetail, 'val');

								break;
							case 'order':
								$seriesOrder = self::getAttributeInteger($seriesDetail, 'val');
								if ($seriesOrder !== null) {
									$plotOrder[$seriesIndex] = $seriesOrder;
								}

								break;
							case 'tx':
								$temp = $this->chartDataSeriesValueSet($seriesDetail);
								if ($temp !== null) {
									$seriesLabel[$seriesIndex] = $temp;
								}

								break;
							case 'spPr':
								$children = $seriesDetail->children($this->aNamespace);
								if (isset($children->ln)) {
									$ln = $children->ln;
									if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
										$noFill = true;
									}
									$lineStyle = new GridLines();
									$this->readLineStyle($seriesDetails, $lineStyle);
								}
								if (isset($children->effectLst)) {
									if ($lineStyle === null) {
										$lineStyle = new GridLines();
									}
									$this->readEffects($seriesDetails, $lineStyle);
								}
								if (isset($children->solidFill)) {
									$fillColor = new ChartColor($this->readColor($children->solidFill));
								}

								break;
							case 'dPt':
								$dptIdx = (int) self::getAttributeString($seriesDetail->idx, 'val');
								if (isset($seriesDetail->spPr)) {
									$children = $seriesDetail->spPr->children($this->aNamespace);
									if (isset($children->solidFill)) {
										$arrayColors = $this->readColor($children->solidFill);
										$dptColors[$dptIdx] = new ChartColor($arrayColors);
									}
								}

								break;
							case 'trendline':
								$trendLine = new TrendLine();
								$this->readLineStyle($seriesDetail, $trendLine);
								$trendLineType = self::getAttributeString($seriesDetail->trendlineType, 'val');
								$dispRSqr = self::getAttributeBoolean($seriesDetail->dispRSqr, 'val');
								$dispEq = self::getAttributeBoolean($seriesDetail->dispEq, 'val');
								$order = self::getAttributeInteger($seriesDetail->order, 'val');
								$period = self::getAttributeInteger($seriesDetail->period, 'val');
								$forward = self::getAttributeFloat($seriesDetail->forward, 'val');
								$backward = self::getAttributeFloat($seriesDetail->backward, 'val');
								$intercept = self::getAttributeFloat($seriesDetail->intercept, 'val');
								$name = (string) $seriesDetail->name;
								$trendLine->setTrendLineProperties(
									$trendLineType,
									$order,
									$period,
									$dispRSqr,
									$dispEq,
									$backward,
									$forward,
									$intercept,
									$name
								);
								$trendLines[] = $trendLine;

								break;
							case 'marker':
								$marker = self::getAttributeString($seriesDetail->symbol, 'val');
								$pointSize = self::getAttributeString($seriesDetail->size, 'val');
								$pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
								if (isset($seriesDetail->spPr)) {
									$children = $seriesDetail->spPr->children($this->aNamespace);
									if (isset($children->solidFill)) {
										$markerFillColor = $this->readColor($children->solidFill);
									}
									if (isset($children->ln->solidFill)) {
										$markerBorderColor = $this->readColor($children->ln->solidFill);
									}
								}

								break;
							case 'smooth':
								$smoothLine = self::getAttributeBoolean($seriesDetail, 'val') ?? false;

								break;
							case 'cat':
								$temp = $this->chartDataSeriesValueSet($seriesDetail);
								if ($temp !== null) {
									$seriesCategory[$seriesIndex] = $temp;
								}

								break;
							case 'val':
								$temp = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
								if ($temp !== null) {
									$seriesValues[$seriesIndex] = $temp;
								}

								break;
							case 'xVal':
								$temp = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
								if ($temp !== null) {
									$seriesCategory[$seriesIndex] = $temp;
								}

								break;
							case 'yVal':
								$temp = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
								if ($temp !== null) {
									$seriesValues[$seriesIndex] = $temp;
								}

								break;
							case 'bubbleSize':
								$seriesBubble = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
								if ($seriesBubble !== null) {
									$seriesBubbles[$seriesIndex] = $seriesBubble;
								}

								break;
							case 'bubble3D':
								$bubble3D = self::getAttributeBoolean($seriesDetail, 'val');

								break;
							case 'dLbls':
								$labelLayout = new Layout($this->readChartAttributes($seriesDetails));

								break;
						}
					}
					if ($labelLayout) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->setLabelLayout($labelLayout);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->setLabelLayout($labelLayout);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->setLabelLayout($labelLayout);
						}
					}
					if ($noFill) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->setScatterLines(false);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->setScatterLines(false);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->setScatterLines(false);
						}
					}
					if ($lineStyle !== null) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->copyLineStyles($lineStyle);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->copyLineStyles($lineStyle);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->copyLineStyles($lineStyle);
						}
					}
					if ($bubble3D) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->setBubble3D($bubble3D);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->setBubble3D($bubble3D);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->setBubble3D($bubble3D);
						}
					}
					if (!empty($dptColors)) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->setFillColor($dptColors);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->setFillColor($dptColors);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->setFillColor($dptColors);
						}
					}
					if ($markerFillColor !== null) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
						}
					}
					if ($markerBorderColor !== null) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
						}
					}
					if ($smoothLine) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->setSmoothLine(true);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->setSmoothLine(true);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->setSmoothLine(true);
						}
					}
					if (!empty($trendLines)) {
						if (isset($seriesLabel[$seriesIndex])) {
							$seriesLabel[$seriesIndex]->setTrendLines($trendLines);
						}
						if (isset($seriesCategory[$seriesIndex])) {
							$seriesCategory[$seriesIndex]->setTrendLines($trendLines);
						}
						if (isset($seriesValues[$seriesIndex])) {
							$seriesValues[$seriesIndex]->setTrendLines($trendLines);
						}
					}
			}
		}
		$series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $plotDirection, $smoothLine);
		$series->setPlotBubbleSizes($seriesBubbles);

		return $series;
	}

	private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null): ?DataSeriesValues
	{
		if (isset($seriesDetail->strRef)) {
			$seriesSource = (string) $seriesDetail->strRef->f;
			$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");

			if (isset($seriesDetail->strRef->strCache)) {
				/** @var array{formatCode: string, dataValues: mixed[]} */
				$seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's');
				$seriesValues
					->setFormatCode($seriesData['formatCode'])
					->setDataValues($seriesData['dataValues']);
			}

			return $seriesValues;
		} elseif (isset($seriesDetail->numRef)) {
			$seriesSource = (string) $seriesDetail->numRef->f;
			$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
			if (isset($seriesDetail->numRef->numCache)) {
				/** @var array{formatCode: string, dataValues: mixed[]} */
				$seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace));
				$seriesValues
					->setFormatCode($seriesData['formatCode'])
					->setDataValues($seriesData['dataValues']);
			}

			return $seriesValues;
		} elseif (isset($seriesDetail->multiLvlStrRef)) {
			$seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
			$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");

			if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
				/** @var array{formatCode: string, dataValues: mixed[]} */
				$seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's');
				$seriesValues
					->setFormatCode($seriesData['formatCode'])
					->setDataValues($seriesData['dataValues']);
			}

			return $seriesValues;
		} elseif (isset($seriesDetail->multiLvlNumRef)) {
			$seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
			$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");

			if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
				/** @var array{formatCode: string, dataValues: mixed[]} */
				$seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's');
				$seriesValues
					->setFormatCode($seriesData['formatCode'])
					->setDataValues($seriesData['dataValues']);
			}

			return $seriesValues;
		}

		if (isset($seriesDetail->v)) {
			return new DataSeriesValues(
				DataSeriesValues::DATASERIES_TYPE_STRING,
				null,
				null,
				1,
				[(string) $seriesDetail->v]
			);
		}

		return null;
	}

	/** @return mixed[] */
	private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
	{
		$seriesVal = [];
		$formatCode = '';
		$pointCount = 0;

		foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
			$seriesValue = Xlsx::testSimpleXml($seriesValue);
			switch ($seriesValueIdx) {
				case 'ptCount':
					$pointCount = self::getAttributeInteger($seriesValue, 'val');

					break;
				case 'formatCode':
					$formatCode = (string) $seriesValue;

					break;
				case 'pt':
					$pointVal = self::getAttributeInteger($seriesValue, 'idx');
					if ($dataType == 's') {
						$seriesVal[$pointVal] = (string) $seriesValue->v;
					} elseif ((string) $seriesValue->v === ExcelError::NA()) {
						$seriesVal[$pointVal] = null;
					} else {
						$seriesVal[$pointVal] = (float) $seriesValue->v;
					}

					break;
			}
		}

		return [
			'formatCode' => $formatCode,
			'pointCount' => $pointCount,
			'dataValues' => $seriesVal,
		];
	}

	/** @return mixed[] */
	private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
	{
		$seriesVal = [];
		$formatCode = '';
		$pointCount = 0;

		foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
			foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
				$seriesValue = Xlsx::testSimpleXml($seriesValue);
				switch ($seriesValueIdx) {
					case 'ptCount':
						$pointCount = self::getAttributeInteger($seriesValue, 'val');

						break;
					case 'formatCode':
						$formatCode = (string) $seriesValue;

						break;
					case 'pt':
						$pointVal = self::getAttributeInteger($seriesValue, 'idx');
						if ($dataType == 's') {
							$seriesVal[$pointVal][] = (string) $seriesValue->v;
						} elseif ((string) $seriesValue->v === ExcelError::NA()) {
							$seriesVal[$pointVal] = null;
						} else {
							$seriesVal[$pointVal][] = (float) $seriesValue->v;
						}

						break;
				}
			}
		}

		return [
			'formatCode' => $formatCode,
			'pointCount' => $pointCount,
			'dataValues' => $seriesVal,
		];
	}

	private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
	{
		$value = new RichText();
		$defaultFontSize = null;
		$defaultBold = null;
		$defaultItalic = null;
		$defaultUnderscore = null;
		$defaultStrikethrough = null;
		$defaultBaseline = null;
		$defaultFontName = null;
		$defaultLatin = null;
		$defaultEastAsian = null;
		$defaultComplexScript = null;
		$defaultFontColor = null;
		if (isset($titleDetailPart->pPr->defRPr)) {
			$defaultFontSize = self::getAttributeInteger($titleDetailPart->pPr->defRPr, 'sz');
			$defaultBold = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'b');
			$defaultItalic = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'i');
			$defaultUnderscore = self::getAttributeString($titleDetailPart->pPr->defRPr, 'u');
			$defaultStrikethrough = self::getAttributeString($titleDetailPart->pPr->defRPr, 'strike');
			$defaultBaseline = self::getAttributeInteger($titleDetailPart->pPr->defRPr, 'baseline');
			if (isset($titleDetailPart->defRPr->rFont['val'])) {
				$defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
			}
			if (isset($titleDetailPart->pPr->defRPr->latin)) {
				$defaultLatin = self::getAttributeString($titleDetailPart->pPr->defRPr->latin, 'typeface');
			}
			if (isset($titleDetailPart->pPr->defRPr->ea)) {
				$defaultEastAsian = self::getAttributeString($titleDetailPart->pPr->defRPr->ea, 'typeface');
			}
			if (isset($titleDetailPart->pPr->defRPr->cs)) {
				$defaultComplexScript = self::getAttributeString($titleDetailPart->pPr->defRPr->cs, 'typeface');
			}
			if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
				$defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill);
			}
		}
		foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
			if (
				(string) $titleDetailElementKey !== 'r'
				|| !isset($titleDetailElement->t)
			) {
				continue;
			}
			$objText = $value->createTextRun((string) $titleDetailElement->t);
			if ($objText->getFont() === null) {
				// @codeCoverageIgnoreStart
				continue;
				// @codeCoverageIgnoreEnd
			}
			$fontSize = null;
			$bold = null;
			$italic = null;
			$underscore = null;
			$strikethrough = null;
			$baseline = null;
			$fontName = null;
			$latinName = null;
			$eastAsian = null;
			$complexScript = null;
			$fontColor = null;
			$underlineColor = null;
			if (isset($titleDetailElement->rPr)) {
				// not used now, not sure it ever was, grandfathering
				if (isset($titleDetailElement->rPr->rFont['val'])) {
					// @codeCoverageIgnoreStart
					$fontName = (string) $titleDetailElement->rPr->rFont['val'];
					// @codeCoverageIgnoreEnd
				}
				if (isset($titleDetailElement->rPr->latin)) {
					$latinName = self::getAttributeString($titleDetailElement->rPr->latin, 'typeface');
				}
				if (isset($titleDetailElement->rPr->ea)) {
					$eastAsian = self::getAttributeString($titleDetailElement->rPr->ea, 'typeface');
				}
				if (isset($titleDetailElement->rPr->cs)) {
					$complexScript = self::getAttributeString($titleDetailElement->rPr->cs, 'typeface');
				}
				$fontSize = self::getAttributeInteger($titleDetailElement->rPr, 'sz');

				// not used now, not sure it ever was, grandfathering
				if (isset($titleDetailElement->rPr->solidFill)) {
					$fontColor = $this->readColor($titleDetailElement->rPr->solidFill);
				}

				$bold = self::getAttributeBoolean($titleDetailElement->rPr, 'b');
				$italic = self::getAttributeBoolean($titleDetailElement->rPr, 'i');
				$baseline = self::getAttributeInteger($titleDetailElement->rPr, 'baseline');
				$underscore = self::getAttributeString($titleDetailElement->rPr, 'u');
				if (isset($titleDetailElement->rPr->uFill->solidFill)) {
					$underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill);
				}

				$strikethrough = self::getAttributeString($titleDetailElement->rPr, 'strike');
			}

			$fontFound = false;
			$latinName = $latinName ?? $defaultLatin;
			if ($latinName !== null) {
				$objText->getFont()->setLatin($latinName);
				$fontFound = true;
			}
			$eastAsian = $eastAsian ?? $defaultEastAsian;
			if ($eastAsian !== null) {
				$objText->getFont()->setEastAsian($eastAsian);
				$fontFound = true;
			}
			$complexScript = $complexScript ?? $defaultComplexScript;
			if ($complexScript !== null) {
				$objText->getFont()->setComplexScript($complexScript);
				$fontFound = true;
			}
			$fontName = $fontName ?? $defaultFontName;
			if ($fontName !== null) {
				// @codeCoverageIgnoreStart
				$objText->getFont()->setName($fontName);
				$fontFound = true;
				// @codeCoverageIgnoreEnd
			}

			$fontSize = $fontSize ?? $defaultFontSize;
			if (is_int($fontSize)) {
				$objText->getFont()->setSize(floor($fontSize / 100));
				$fontFound = true;
			} else {
				$objText->getFont()->setSize(null, true);
			}

			$fontColor = $fontColor ?? $defaultFontColor;
			if (!empty($fontColor)) {
				$objText->getFont()->setChartColor($fontColor);
				$fontFound = true;
			}

			$bold = $bold ?? $defaultBold;
			if ($bold !== null) {
				$objText->getFont()->setBold($bold);
				$fontFound = true;
			}

			$italic = $italic ?? $defaultItalic;
			if ($italic !== null) {
				$objText->getFont()->setItalic($italic);
				$fontFound = true;
			}

			$baseline = $baseline ?? $defaultBaseline;
			if ($baseline !== null) {
				$objText->getFont()->setBaseLine($baseline);
				if ($baseline > 0) {
					$objText->getFont()->setSuperscript(true);
				} elseif ($baseline < 0) {
					$objText->getFont()->setSubscript(true);
				}
				$fontFound = true;
			}

			$underscore = $underscore ?? $defaultUnderscore;
			if ($underscore !== null) {
				if ($underscore == 'sng') {
					$objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
				} elseif ($underscore == 'dbl') {
					$objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
				} elseif ($underscore !== '') {
					$objText->getFont()->setUnderline($underscore);
				} else {
					$objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
				}
				$fontFound = true;
				if ($underlineColor) {
					$objText->getFont()->setUnderlineColor($underlineColor);
				}
			}

			$strikethrough = $strikethrough ?? $defaultStrikethrough;
			if ($strikethrough !== null) {
				$objText->getFont()->setStrikeType($strikethrough);
				if ($strikethrough == 'noStrike') {
					$objText->getFont()->setStrikethrough(false);
				} else {
					$objText->getFont()->setStrikethrough(true);
				}
				$fontFound = true;
			}
			if ($fontFound === false) {
				$objText->setFont(null);
			}
		}

		return $value;
	}

	private function parseFont(SimpleXMLElement $titleDetailPart): ?Font
	{
		if (!isset($titleDetailPart->pPr->defRPr)) {
			return null;
		}
		$fontArray = [];
		$fontArray['size'] = self::getAttributeInteger($titleDetailPart->pPr->defRPr, 'sz');
		if ($fontArray['size'] !== null && $fontArray['size'] >= 100) {
			$fontArray['size'] /= 100.0;
		}
		if ($fontArray['size'] !== null) {
			$fontArray['size'] = (int) ($fontArray['size']);
		}
		$fontArray['bold'] = (bool) self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'b');
		$fontArray['italic'] = (bool) self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'i');
		$fontArray['underscore'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'u');
		$strikethrough = self::getAttributeString($titleDetailPart->pPr->defRPr, 'strike');
		if ($strikethrough !== null) {
			if ($strikethrough == 'noStrike') {
				$fontArray['strikethrough'] = false;
			} else {
				$fontArray['strikethrough'] = true;
			}
		}
		$fontArray['cap'] = (string) self::getAttributeString($titleDetailPart->pPr->defRPr, 'cap');

		if (isset($titleDetailPart->pPr->defRPr->latin)) {
			$fontArray['latin'] = (string) self::getAttributeString($titleDetailPart->pPr->defRPr->latin, 'typeface');
		}
		if (isset($titleDetailPart->pPr->defRPr->ea)) {
			$fontArray['eastAsian'] = (string) self::getAttributeString($titleDetailPart->pPr->defRPr->ea, 'typeface');
		}
		if (isset($titleDetailPart->pPr->defRPr->cs)) {
			$fontArray['complexScript'] = (string) self::getAttributeString($titleDetailPart->pPr->defRPr->cs, 'typeface');
		}
		if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
			$fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill));
		}
		$font = new Font();
		//$font->setSize(null, true);
		$font->applyFromArray($fontArray);

		return $font;
	}

	/** @return mixed[] */
	private function readChartAttributes(?SimpleXMLElement $chartDetail): array
	{
		$plotAttributes = [];
		if (isset($chartDetail->dLbls)) {
			if (isset($chartDetail->dLbls->dLblPos)) {
				$plotAttributes['dLblPos'] = self::getAttributeString($chartDetail->dLbls->dLblPos, 'val');
			}
			if (isset($chartDetail->dLbls->numFmt)) {
				$plotAttributes['numFmtCode'] = self::getAttributeString($chartDetail->dLbls->numFmt, 'formatCode');
				$plotAttributes['numFmtLinked'] = self::getAttributeBoolean($chartDetail->dLbls->numFmt, 'sourceLinked');
			}
			if (isset($chartDetail->dLbls->showLegendKey)) {
				$plotAttributes['showLegendKey'] = self::getAttributeString($chartDetail->dLbls->showLegendKey, 'val');
			}
			if (isset($chartDetail->dLbls->showVal)) {
				$plotAttributes['showVal'] = self::getAttributeString($chartDetail->dLbls->showVal, 'val');
			}
			if (isset($chartDetail->dLbls->showCatName)) {
				$plotAttributes['showCatName'] = self::getAttributeString($chartDetail->dLbls->showCatName, 'val');
			}
			if (isset($chartDetail->dLbls->showSerName)) {
				$plotAttributes['showSerName'] = self::getAttributeString($chartDetail->dLbls->showSerName, 'val');
			}
			if (isset($chartDetail->dLbls->showPercent)) {
				$plotAttributes['showPercent'] = self::getAttributeString($chartDetail->dLbls->showPercent, 'val');
			}
			if (isset($chartDetail->dLbls->showBubbleSize)) {
				$plotAttributes['showBubbleSize'] = self::getAttributeString($chartDetail->dLbls->showBubbleSize, 'val');
			}
			if (isset($chartDetail->dLbls->showLeaderLines)) {
				$plotAttributes['showLeaderLines'] = self::getAttributeString($chartDetail->dLbls->showLeaderLines, 'val');
			}
			if (isset($chartDetail->dLbls->spPr)) {
				$sppr = $chartDetail->dLbls->spPr->children($this->aNamespace);
				if (isset($sppr->solidFill)) {
					$plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill));
				}
				if (isset($sppr->ln->solidFill)) {
					$plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill));
				}
			}
			if (isset($chartDetail->dLbls->txPr)) {
				$txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
				if (isset($txpr->p)) {
					$plotAttributes['labelFont'] = $this->parseFont($txpr->p);
					if (isset($txpr->p->pPr->defRPr->effectLst)) {
						$labelEffects = new GridLines();
						$this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false);
						$plotAttributes['labelEffects'] = $labelEffects;
					}
				}
			}
		}

		return $plotAttributes;
	}

	/** @param array<mixed> $plotAttributes */
	private function setChartAttributes(Layout $plotArea, array $plotAttributes): void
	{
		foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
			/** @var ?bool $plotAttributeValue */
			switch ($plotAttributeKey) {
				case 'showLegendKey':
					$plotArea->setShowLegendKey($plotAttributeValue);

					break;
				case 'showVal':
					$plotArea->setShowVal($plotAttributeValue);

					break;
				case 'showCatName':
					$plotArea->setShowCatName($plotAttributeValue);

					break;
				case 'showSerName':
					$plotArea->setShowSerName($plotAttributeValue);

					break;
				case 'showPercent':
					$plotArea->setShowPercent($plotAttributeValue);

					break;
				case 'showBubbleSize':
					$plotArea->setShowBubbleSize($plotAttributeValue);

					break;
				case 'showLeaderLines':
					$plotArea->setShowLeaderLines($plotAttributeValue);

					break;
				case 'labelFont':
					/** @var ?Font $plotAttributeValue */
					$plotArea->setLabelFont($plotAttributeValue);

					break;
			}
		}
	}

	private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void
	{
		if (!isset($chartObject)) {
			return;
		}
		if ($getSppr) {
			if (!isset($chartDetail->spPr)) {
				return;
			}
			$sppr = $chartDetail->spPr->children($this->aNamespace);
		} else {
			$sppr = $chartDetail;
		}
		if (isset($sppr->effectLst->glow)) {
			$axisGlowSize = (float) self::getAttributeInteger($sppr->effectLst->glow, 'rad') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
			if ($axisGlowSize != 0.0) {
				$colorArray = $this->readColor($sppr->effectLst->glow);
				$chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
			}
		}

		if (isset($sppr->effectLst->softEdge)) {
			$softEdgeSize = self::getAttributeString($sppr->effectLst->softEdge, 'rad');
			if (is_numeric($softEdgeSize)) {
				$chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize));
			}
		}

		$type = '';
		foreach (self::SHADOW_TYPES as $shadowType) {
			if (isset($sppr->effectLst->$shadowType)) {
				$type = $shadowType;

				break;
			}
		}
		if ($type !== '') {
			$blur = self::getAttributeString($sppr->effectLst->$type, 'blurRad');
			$blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null;
			$dist = self::getAttributeString($sppr->effectLst->$type, 'dist');
			$dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null;
			$direction = self::getAttributeString($sppr->effectLst->$type, 'dir');
			$direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null;
			$algn = self::getAttributeString($sppr->effectLst->$type, 'algn');
			$rot = self::getAttributeString($sppr->effectLst->$type, 'rotWithShape');
			$size = [];
			foreach (['sx', 'sy'] as $sizeType) {
				$sizeValue = self::getAttributeString($sppr->effectLst->$type, $sizeType);
				if (is_numeric($sizeValue)) {
					$size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue);
				} else {
					$size[$sizeType] = null;
				}
			}
			foreach (['kx', 'ky'] as $sizeType) {
				$sizeValue = self::getAttributeString($sppr->effectLst->$type, $sizeType);
				if (is_numeric($sizeValue)) {
					$size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue);
				} else {
					$size[$sizeType] = null;
				}
			}
			$colorArray = $this->readColor($sppr->effectLst->$type);
			$chartObject
				->setShadowProperty('effect', $type)
				->setShadowProperty('blur', $blur)
				->setShadowProperty('direction', $direction)
				->setShadowProperty('distance', $dist)
				->setShadowProperty('algn', $algn)
				->setShadowProperty('rotWithShape', $rot)
				->setShadowProperty('size', $size)
				->setShadowProperty('color', $colorArray);
		}
	}

	private const SHADOW_TYPES = [
		'outerShdw',
		'innerShdw',
	];

	/** @return array{type: ?string, value: ?string, alpha: ?int, brightness: ?int} */
	private function readColor(SimpleXMLElement $colorXml): array
	{
		$result = [
			'type' => null,
			'value' => null,
			'alpha' => null,
			'brightness' => null,
		];
		foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
			if (isset($colorXml->$type)) {
				$result['type'] = $type;
				$result['value'] = self::getAttributeString($colorXml->$type, 'val');
				if (isset($colorXml->$type->alpha)) {
					$alpha = self::getAttributeString($colorXml->$type->alpha, 'val');
					if (is_numeric($alpha)) {
						$result['alpha'] = ChartColor::alphaFromXml($alpha);
					}
				}
				if (isset($colorXml->$type->lumMod)) {
					$brightness = self::getAttributeString($colorXml->$type->lumMod, 'val');
					if (is_numeric($brightness)) {
						$result['brightness'] = ChartColor::alphaFromXml($brightness);
					}
				}

				break;
			}
		}

		return $result;
	}

	private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
	{
		if (!isset($chartObject, $chartDetail->spPr)) {
			return;
		}
		$sppr = $chartDetail->spPr->children($this->aNamespace);

		if (!isset($sppr->ln)) {
			return;
		}
		$lineWidth = null;
		$lineWidthTemp = self::getAttributeString($sppr->ln, 'w');
		if (is_numeric($lineWidthTemp)) {
			$lineWidth = ChartProperties::xmlToPoints($lineWidthTemp);
		}
		/** @var string $compoundType */
		$compoundType = self::getAttributeString($sppr->ln, 'cmpd');
		/** @var string $dashType */
		$dashType = self::getAttributeString($sppr->ln->prstDash, 'val');
		/** @var string $capType */
		$capType = self::getAttributeString($sppr->ln, 'cap');
		if (isset($sppr->ln->miter)) {
			$joinType = ChartProperties::LINE_STYLE_JOIN_MITER;
		} elseif (isset($sppr->ln->bevel)) {
			$joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL;
		} else {
			$joinType = '';
		}
		$headArrowSize = 0;
		$endArrowSize = 0;
		$headArrowType = self::getAttributeString($sppr->ln->headEnd, 'type');
		$headArrowWidth = self::getAttributeString($sppr->ln->headEnd, 'w');
		$headArrowLength = self::getAttributeString($sppr->ln->headEnd, 'len');
		$endArrowType = self::getAttributeString($sppr->ln->tailEnd, 'type');
		$endArrowWidth = self::getAttributeString($sppr->ln->tailEnd, 'w');
		$endArrowLength = self::getAttributeString($sppr->ln->tailEnd, 'len');
		$chartObject->setLineStyleProperties(
			$lineWidth,
			$compoundType,
			$dashType,
			$capType,
			$joinType,
			$headArrowType,
			$headArrowSize,
			$endArrowType,
			$endArrowSize,
			$headArrowWidth,
			$headArrowLength,
			$endArrowWidth,
			$endArrowLength
		);
		$colorArray = $this->readColor($sppr->ln->solidFill);
		$chartObject->getLineColor()->setColorPropertiesArray($colorArray);
	}

	private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void
	{
		if (!isset($whichAxis)) {
			return;
		}
		if (isset($chartDetail->delete)) {
			$whichAxis->setAxisOption('hidden', (string) self::getAttributeString($chartDetail->delete, 'val'));
		}
		if (isset($chartDetail->numFmt)) {
			$whichAxis->setAxisNumberProperties(
				(string) self::getAttributeString($chartDetail->numFmt, 'formatCode'),
				null,
				(int) self::getAttributeInteger($chartDetail->numFmt, 'sourceLinked')
			);
		}
		if (isset($chartDetail->crossBetween)) {
			$whichAxis->setCrossBetween((string) self::getAttributeString($chartDetail->crossBetween, 'val'));
		}
		if (isset($chartDetail->dispUnits, $chartDetail->dispUnits->builtInUnit)) {
			$whichAxis->setAxisOption('dispUnitsBuiltIn', (string) self::getAttributeString($chartDetail->dispUnits->builtInUnit, 'val'));
			if (isset($chartDetail->dispUnits->dispUnitsLbl)) {
				$whichAxis->setDispUnitsTitle(new Title());
				// TODO parse title elements
			}
		}
		if (isset($chartDetail->majorTickMark)) {
			$whichAxis->setAxisOption('major_tick_mark', (string) self::getAttributeString($chartDetail->majorTickMark, 'val'));
		}
		if (isset($chartDetail->minorTickMark)) {
			$whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttributeString($chartDetail->minorTickMark, 'val'));
		}
		if (isset($chartDetail->tickLblPos)) {
			$whichAxis->setAxisOption('axis_labels', (string) self::getAttributeString($chartDetail->tickLblPos, 'val'));
		}
		if (isset($chartDetail->crosses)) {
			$whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttributeString($chartDetail->crosses, 'val'));
		}
		if (isset($chartDetail->crossesAt)) {
			$whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttributeString($chartDetail->crossesAt, 'val'));
		}
		if (isset($chartDetail->scaling->logBase)) {
			$whichAxis->setAxisOption('logBase', (string) self::getAttributeString($chartDetail->scaling->logBase, 'val'));
		}
		if (isset($chartDetail->scaling->orientation)) {
			$whichAxis->setAxisOption('orientation', (string) self::getAttributeString($chartDetail->scaling->orientation, 'val'));
		}
		if (isset($chartDetail->scaling->max)) {
			$whichAxis->setAxisOption('maximum', (string) self::getAttributeString($chartDetail->scaling->max, 'val'));
		}
		if (isset($chartDetail->scaling->min)) {
			$whichAxis->setAxisOption('minimum', (string) self::getAttributeString($chartDetail->scaling->min, 'val'));
		}
		if (isset($chartDetail->scaling->min)) {
			$whichAxis->setAxisOption('minimum', (string) self::getAttributeString($chartDetail->scaling->min, 'val'));
		}
		if (isset($chartDetail->majorUnit)) {
			$whichAxis->setAxisOption('major_unit', (string) self::getAttributeString($chartDetail->majorUnit, 'val'));
		}
		if (isset($chartDetail->minorUnit)) {
			$whichAxis->setAxisOption('minor_unit', (string) self::getAttributeString($chartDetail->minorUnit, 'val'));
		}
		if (isset($chartDetail->baseTimeUnit)) {
			$whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttributeString($chartDetail->baseTimeUnit, 'val'));
		}
		if (isset($chartDetail->majorTimeUnit)) {
			$whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttributeString($chartDetail->majorTimeUnit, 'val'));
		}
		if (isset($chartDetail->minorTimeUnit)) {
			$whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttributeString($chartDetail->minorTimeUnit, 'val'));
		}
		if (isset($chartDetail->txPr)) {
			$children = $chartDetail->txPr->children($this->aNamespace);
			$addAxisText = false;
			$axisText = new AxisText();
			if (isset($children->bodyPr)) {
				$textRotation = self::getAttributeString($children->bodyPr, 'rot');
				if (is_numeric($textRotation)) {
					$axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation));
					$addAxisText = true;
				}
			}
			if (isset($children->p->pPr->defRPr)) {
				$font = $this->parseFont($children->p);
				if ($font !== null) {
					$axisText->setFont($font);
					$addAxisText = true;
				}
			}
			if (isset($children->p->pPr->defRPr->effectLst)) {
				$this->readEffects($children->p->pPr->defRPr, $axisText, false);
				$addAxisText = true;
			}
			if ($addAxisText) {
				$whichAxis->setAxisText($axisText);
			}
		}
	}
}
