<?php
namespace nur\sery\ext\spreadsheet;

use nur\sery\file\csv\AbstractBuilder;
use nur\sery\file\csv\TAbstractBuilder;
use nur\sery\os\path;
use nur\sery\php\nur_func;
use nur\sery\php\time\Date;
use nur\sery\php\time\DateTime;
use nur\sery\web\http;
use OpenSpout\Common\Entity\Cell;
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Common\Helper\CellTypeHelper;
use OpenSpout\Writer\Common\Creator\WriterEntityFactory;
use OpenSpout\Writer\WriterMultiSheetsAbstract;
use OpenSpout\Writer\XLSX\Entity\SheetView;

class SpoutBuilder extends AbstractBuilder {
  use TAbstractBuilder;

  const DATE_FORMAT = "mm/dd/yyyy";

  const DATETIME_FORMAT = "mm/dd/yyyy hh:mm:ss";

  /** @var bool faut-il choisir le type numérique pour une chaine numérique? */
  const TYPE_NUMERIC = true;

  /** @var bool faut-il choisir le type date pour une chaine au bon format? */
  const TYPE_DATE = true;

  /** @var string|int|null nom de la feuille dans laquelle écrire */
  const WSNAME = null;

  function __construct(?string $output, ?array $params=null) {
    parent::__construct($output, $params);
    $ssType = $params["ss_type"] ?? null;
    if ($ssType === null) {
      switch (path::ext($this->output)) {
      case ".ods":
        $ssType = "ods";
        break;
      case ".xlsx":
      default:
        $ssType = "xlsx";
        break;
      }
    }
    switch ($ssType) {
    case "ods":
      $ss = WriterEntityFactory::createODSWriter();
      break;
    case "xlsx":
    default:
      $ss = WriterEntityFactory::createXLSXWriter();
      break;
    }
    $ss->setDefaultColumnWidth(10.5);
    $ss->writeToStream($this->getResource());
    $this->ss = $ss;
    $this->typeNumeric = boolval($params["type_numeric"] ?? static::TYPE_NUMERIC);
    $this->typeDate = boolval($params["type_date"] ?? static::TYPE_DATE);
    $this->firstSheet = true;
    $this->setWsname($params["wsname"] ?? static::WSNAME);
  }

  protected WriterMultiSheetsAbstract $ss;

  protected bool $typeNumeric;

  protected bool $typeDate;

  const STYLE_ROW = 0, STYLE_HEADER = 1;

  protected int $rowStyle;

  protected bool $firstSheet;

  /**
   * @param string|int|null $wsname
   */
  function setWsname($wsname, ?array $params=null): self {
    $ss = $this->ss;
    $this->rowStyle = self::STYLE_ROW;
    if ($this->firstSheet) {
      $this->firstSheet = false;
      $ws = $ss->getCurrentSheet();
    } else {
      $ws = $ss->addNewSheetAndMakeItCurrent();
      $this->wroteHeaders = false;
      $this->built = false;
    }
    $wsname ??= $params["wsname"] ?? null;
    if ($wsname !== null) $ws->setName($wsname);
    $sheetView = (new SheetView())
      ->setFreezeRow(2);
    $ws->setSheetView($sheetView);
    if ($params !== null) {
      if (array_key_exists("schema", $params)) {
        $this->schema = $params["schema"] ?? null;
      }
      if (array_key_exists("headers", $params)) {
        $this->headers = $params["headers"] ?? null;
      }
      if (array_key_exists("rows", $params)) {
        $rows = $params["rows"] ?? null;
        if (is_callable($rows)) $rows = $rows();
        $this->rows = $rows;
      }
      if (array_key_exists("cook_func", $params)) {
        $cookFunc = $params["cook_func"] ?? null;
        $cookCtx = $cookArgs = null;
        if ($cookFunc !== null) {
          nur_func::ensure_func($cookFunc, $this, $cookArgs);
          $cookCtx = nur_func::_prepare($cookFunc);
        }
        $this->cookCtx = $cookCtx;
        $this->cookArgs = $cookArgs;
      }
      if (array_key_exists("type_numeric", $params)) {
        $this->typeNumeric = boolval($params["type_numeric"] ?? static::TYPE_NUMERIC);
      }
      if (array_key_exists("type_date", $params)) {
        $this->typeDate = boolval($params["type_date"] ?? static::TYPE_DATE);
      }
    }
    return $this;
  }

  protected function isNumeric($value): bool {
    if ($this->typeNumeric && is_numeric($value)) return true;
    if (!is_string($value) && is_numeric($value)) return true;
    return false;
  }

  protected function isDate(&$value, &$style): bool {
    if (CellTypeHelper::isDateTimeOrDateInterval($value)) {
      $style = (new Style())->setFormat(self::DATE_FORMAT);
      return true;
    }
    if (!is_string($value) || !$this->typeDate) return false;
    if (DateTime::isa_datetime($value, true)) {
      $value = new DateTime($value);
      $style = (new Style())->setFormat(self::DATETIME_FORMAT);
      return true;
    }
    if (DateTime::isa_date($value, true)) {
      $value = new Date($value);
      $style = (new Style())->setFormat(self::DATE_FORMAT);
      return true;
    }
    return false;
  }

  function _write(array $row): void {
    $cells = [];
    $rowStyle = null;
    foreach ($row as $col) {
      $style = null;
      if ($col === null || $col === "") {
        $type = Cell::TYPE_EMPTY;
      } elseif ($this->isNumeric($col)) {
        $type = Cell::TYPE_NUMERIC;
      } elseif ($this->isDate($col, $style)) {
        $type = Cell::TYPE_DATE;
      } else {
        $type = Cell::TYPE_STRING;
      }
      $cell = WriterEntityFactory::createCell($col, $style);
      $cell->setType($type);
      $cells[] = $cell;
    }
    if ($this->rowStyle === self::STYLE_HEADER) {
      $rowStyle = (new Style())->setFontBold();
    }
    $this->ss->addRow(WriterEntityFactory::createRow($cells, $rowStyle));
  }

  function writeHeaders(?array $headers=null): void {
    $this->rowStyle = self::STYLE_HEADER;
    parent::writeHeaders($headers);
    $this->rowStyle = self::STYLE_ROW;
  }

  function _sendContentType(): void {
    switch (path::ext($this->output)) {
    case ".ods":
      $contentType = "application/vnd.oasis.opendocument.spreadsheet";
      break;
    case ".xlsx":
    default:
      $contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
      break;
    }
    http::content_type($contentType);
  }

  protected function _checkOk(): bool {
    $this->ss->close();
    $this->rewind();
    return true;
  }
}
