<?php

namespace Hubleto\Framework;

abstract class Column implements Interfaces\ColumnInterface, \JsonSerializable
{

  const ALWAYS_VISIBLE = 1;
  const ALWAYS_HIDDEN = 2;
  const DEFAULT_VISIBLE = 3;
  const DEFAULT_HIDDEN = 4;

  protected \Hubleto\Framework\Model $model;

  protected array $indexes = [];

  protected string $type = '';
  protected string $sqlDataType = '';
  protected string $title = '';
  protected bool $readonly = false;
  protected bool $required = false;
  protected int $visibility = 0;
  protected bool $hidden = false;
  protected string $rawSqlDefinition = '';
  protected string $placeholder = '';
  protected string $unit = '';
  protected string $format = '';
  protected string $description = '';
  protected mixed $defaultValue = null;
  protected array $examples = [];
  protected array $enumValues = [];
  protected array $enumCssClasses = [];
  protected array $predefinedValues = [];
  protected string $colorScale = '';
  protected string $cssClass = '';
  protected string $tableCssClass = '';
  protected string $tableCellRenderer = '';
  protected string $lookupModel = '';
  protected string $reactComponent = '';
  protected int $decimals = 0;
  protected float $step = 1;
  protected string $icon = '';
  protected int $byteSize = 14;
  protected array $inputProps = [];
  protected string $endpoint = '';
  protected bool $creatable = false;
  protected string $searchAlgorithm = '';
  protected string $textAlign = 'left';

  protected array $properties = [];

  public function __construct(\Hubleto\Framework\Model $model, string $title)
  {
    $this->model = $model;
    $this->title = $title;
  }

  public function addIndex(string $indexDefinition): Column
  {
    $this->indexes[] = $indexDefinition;
    return $this;
  }

  public function getIndexes(): array
  {
    return $this->indexes;
  }

  public function getProperty(string $pName): mixed { return $this->properties[$pName] ?? null; }
  public function setProperty(string $pName, mixed $pValue): Column { $this->properties[$pName] = $pValue; return $this; }

  public function getReactComponent(): string { return $this->reactComponent; }
  public function setReactComponent(string $reactComponent): Column { $this->reactComponent = $reactComponent; return $this; }

  public function getByteSize(): int { return $this->byteSize; }
  public function setByteSize(int $byteSize): Column { $this->byteSize = $byteSize; return $this; }

  public function getType(): string { return $this->type; }
  public function setType(string $type): Column { $this->type = $type; return $this; }

  public function getTextAlign(): string { return $this->textAlign; }
  public function setTextAlign(string $textAlign): Column { $this->textAlign = $textAlign; return $this; }

  public function getSearchAlgorithm(): string { return $this->searchAlgorithm; }

  public function getSqlDataType(): string { return $this->sqlDataType; }
  public function setSqlDataType(string $sqlDataType): Column { $this->sqlDataType = $sqlDataType; return $this; }

  public function getTitle(): string { return $this->title; }
  public function setTitle(string $title): Column { $this->title = $title; return $this; }

  public function getReadonly(): bool { return $this->readonly; }
  public function setReadonly(bool $readonly = true): Column { $this->readonly = $readonly; return $this; }

  public function getRequired(): bool { return $this->required; }
  public function setRequired(bool $required = true): Column { $this->required = $required; return $this; }

  public function getVisibility(): int { return $this->visibility; }
  public function setVisibility(int $visibility = 0): Column { $this->visibility = $visibility; return $this; }
  public function setAlwaysVisible(): Column { $this->visibility = self::ALWAYS_VISIBLE; return $this; }
  public function setAlwaysHidden(): Column { $this->visibility = self::ALWAYS_HIDDEN; return $this; }
  public function setDefaultVisible(): Column { $this->visibility = self::DEFAULT_VISIBLE; return $this; }
  public function setDefaultHidden(): Column { $this->visibility = self::DEFAULT_HIDDEN; return $this; }

  public function getPlaceholder(): bool { return $this->placeholder; }
  public function setPlaceholder(bool $placeholder = true): Column { $this->placeholder = $placeholder; return $this; }

  public function getUnit(): string { return $this->unit; }
  public function setUnit(string $unit): Column { $this->unit = $unit; return $this; }

  public function getColorScale(): string { return $this->colorScale; }
  public function setColorScale(string $colorScale): Column { $this->colorScale = $colorScale; return $this; }

  public function getCssClass(): string { return $this->cssClass; }
  public function setCssClass(string $cssClass): Column { $this->cssClass = $cssClass; return $this; }

  public function getTableCssClass(): string { return $this->tableCssClass; }
  public function setTableCssClass(string $tableCssClass): Column { $this->tableCssClass = $tableCssClass; return $this; }

  public function getFormat(): bool { return $this->format; }
  public function setFormat(bool $format = true): Column { $this->format = $format; return $this; }

  public function getDescription(): string { return $this->description; }
  public function setDescription(string $description): Column { $this->description = $description; return $this; }

  public function getExamples(): array { return $this->examples; }
  public function setExamples(array $examples): Column { $this->examples = $examples; return $this; }

  public function getEnumValues(): array { return $this->enumValues; }
  public function setEnumValues(array $enumValues): Column { $this->enumValues = $enumValues; return $this; }

  public function getEnumCssClasses(): array { return $this->enumCssClasses; }
  public function setEnumCssClasses(array $enumCssClasses): Column { $this->enumCssClasses = $enumCssClasses; return $this; }

  public function getPredefinedValues(): array { return $this->predefinedValues; }
  public function setPredefinedValues(array $predefinedValues): Column { $this->predefinedValues = $predefinedValues; return $this; }

  public function getHidden(): bool { return $this->hidden; }
  public function setHidden(bool $hidden = true): Column { $this->hidden = $hidden; return $this; }

  public function getRawSqlDefinition(): string { return $this->rawSqlDefinition; }
  public function setRawSqlDefinition(string $rawSqlDefinition): Column { $this->rawSqlDefinition = $rawSqlDefinition; return $this; }

  public function getDefaultValue(): mixed { return $this->defaultValue; }
  public function setDefaultValue(mixed $defaultValue): Column { $this->defaultValue = $defaultValue; return $this; }

  public function getTableCellRenderer(): string { return $this->tableCellRenderer; }
  public function setTableCellRenderer(string $tableCellRenderer): Column { $this->tableCellRenderer = $tableCellRenderer; return $this; }

  public function getLookupModel(): string { return $this->lookupModel; }
  public function setLookupModel(string $lookupModel): Column { $this->lookupModel = $lookupModel; return $this; }

  public function getDecimals(): int { return $this->decimals; }
  public function setDecimals(int $decimals): Column { $this->decimals = $decimals; return $this; }

  public function getStep(): float { return $this->step; }
  public function setStep(float $step): Column { $this->step = $step; return $this; }

  public function getIcon(): string { return $this->icon; }
  public function setIcon(string $icon): Column { $this->icon = $icon; return $this; }

  public function getEndpoint(): string { return $this->endpoint; }
  public function setEndpoint(string $endpoint): Column { $this->endpoint = $endpoint; return $this; }

  public function getCreatable(): bool { return $this->creatable; }
  public function setCreatable(bool $creatable = true): Column { $this->creatable = $creatable; return $this; }

  public function getInputProps(): array { return $this->inputProps; }
  public function setInputProps(array $inputProps): Column { $this->inputProps = $inputProps; return $this; }

  public function setInputProp(string $pName, mixed $pValue): Column {
    $this->inputProps[$pName] = $pValue;
    return $this;
  }

  public function describeInput(): \Hubleto\Framework\Description\Input
  {
    $description = new \Hubleto\Framework\Description\Input();
    $description->setType($this->getType());
    if (!empty($this->getTitle())) $description->setTitle($this->getTitle());
    if (!empty($this->getReactComponent())) $description->setReactComponent($this->getReactComponent());
    if (!empty($this->getCssClass())) $description->setCssClass($this->getCssClass());
    if (!empty($this->getPlaceholder())) $description->setPlaceholder($this->getPlaceholder());
    if (!empty($this->getReadonly())) $description->setReadonly($this->getReadonly());
    if (!empty($this->getRequired())) $description->setRequired($this->getRequired());
    if (!empty($this->getDescription())) $description->setDescription($this->getDescription());
    if (!empty($this->getUnit())) $description->setUnit($this->getUnit());
    if (!empty($this->getFormat())) $description->setFormat($this->getFormat());
    if (!empty($this->getLookupModel())) $description->setLookupModel($this->getLookupModel());
    if (!empty($this->getEndpoint())) $description->setEndpoint($this->getEndpoint());
    if (!empty($this->getCreatable())) $description->setCreatable($this->getCreatable());
    if ($this->defaultValue !== null) $description->setDefaultValue($this->defaultValue);
    $description->setExamples($this->examples);
    $description->setDecimals($this->decimals);
    $description->setStep($this->step);
    $description->setIcon($this->icon);
    $description->setEnumValues($this->enumValues);
    $description->setEnumCssClasses($this->enumCssClasses);
    $description->setPredefinedValues($this->predefinedValues);
    $description->setInputProps($this->inputProps);

    foreach ($this->properties as $pName => $pValue) $description->setProperty($pName, $pValue);

    return $description;
  }

  public function fromArray(array $columnConfig): Column
  {
    if (isset($columnConfig['title'])) $this->setTitle($columnConfig['title']);
    if (isset($columnConfig['reactComponent'])) $this->setReactComponent($columnConfig['reactComponent']);
    if (isset($columnConfig['placeholder'])) $this->setPlaceholder($columnConfig['placeholder']);
    if (isset($columnConfig['readonly'])) $this->setReadonly($columnConfig['readonly']);
    if (isset($columnConfig['required'])) $this->setRequired($columnConfig['required']);
    if (isset($columnConfig['visibility'])) $this->setVisibility($columnConfig['visibility']);
    if (isset($columnConfig['description'])) $this->setDescription($columnConfig['description']);
    if (isset($columnConfig['unit'])) $this->setUnit($columnConfig['unit']);
    if (isset($columnConfig['format'])) $this->setFormat($columnConfig['format']);
    if (isset($columnConfig['defaultValue'])) $this->setDefaultValue($columnConfig['defaultValue']);
    if (isset($columnConfig['examples'])) $this->setExamples($columnConfig['examples']);
    if (isset($columnConfig['enumValues'])) $this->setEnumValues($columnConfig['enumValues']);
    if (isset($columnConfig['cssClass'])) $this->setCssClass($columnConfig['cssClass']);
    if (isset($columnConfig['tableCssClass'])) $this->setTableCssClass($columnConfig['tableCssClass']);
    if (isset($columnConfig['enumCssClasses'])) $this->setEnumCssClasses($columnConfig['enumCssClasses']);
    if (isset($columnConfig['predefinedValues'])) $this->setPredefinedValues($columnConfig['predefinedValues']);
    if (isset($columnConfig['lookupModel'])) $this->setLookupModel($columnConfig['lookupModel']);
    if (isset($columnConfig['decimals'])) $this->setDecimals($columnConfig['decimals']);
    if (isset($columnConfig['step'])) $this->setStep($columnConfig['step']);
    if (isset($columnConfig['icon'])) $this->setIcon($columnConfig['icon']);
    if (isset($columnConfig['byteSize'])) $this->setByteSize($columnConfig['byteSize']);
    if (isset($columnConfig['endpoint'])) $this->setEndpoint($columnConfig['endpoint']);
    if (isset($columnConfig['creatable'])) $this->setCreatable($columnConfig['creatable']);
    return $this;
  }

  public function jsonSerialize(): array
  {
    $column = [
      'type' => $this->type,
      'title' => $this->title,
      'readonly' => $this->readonly,
      'required' => $this->required,
      'visibility' => $this->visibility,
      'defaultValue' => $this->defaultValue,
      'unit' => $this->unit,
      'description' => $this->description,
      'format' => $this->format,
      'placeholder' => $this->placeholder,
      'colorScale' => $this->colorScale,
      'cssClass' => $this->cssClass,
      'tableCssClass' => $this->tableCssClass,
      'tableCellRenderer' => $this->tableCellRenderer,
      'reactComponent' => $this->reactComponent,
      'decimals' => $this->decimals,
      'step' => $this->step,
      'icon' => $this->icon,
      'byteSize' => $this->byteSize,
      'endpoint' => $this->endpoint,
      'creatable' => $this->creatable,
      'textAlign' => $this->textAlign,
    ];

    if (count($this->enumValues) > 0) $column['enumValues'] = $this->enumValues;
    if (count($this->enumCssClasses) > 0) $column['enumCssClasses'] = $this->enumCssClasses;
    if (count($this->predefinedValues) > 0) $column['predefinedValues'] = $this->predefinedValues;

    foreach ($this->properties as $pName => $pValue) {
      if (is_array($pValue)) {
        $column[$pName] = $pValue;
      } else {
        $column[$pName] = (string) $pValue;
      }
    }

    return $column;
  }

  public function toArray(): array
  {
    return $this->jsonSerialize();
  }

  public function getNullValue(): mixed
  {
    return null;
  }

  public function isEmpty(mixed $value): bool
  {
    return $value === null || $value === '';
  }
  
  public function normalize(mixed $value): mixed
  {
    return ($value === null ? $this->defaultValue : $value);
  }

  public function validate(mixed $value): bool
  {
    return TRUE;
  }

  public function sqlCreateString(string $table, string $columnName): string
  {
    return (empty($this->sqlDataType) ? '' : "`{$columnName}` {$this->sqlDataType} " . $this->getRawSqlDefinition());
  }

  public function sqlIndexString(string $table, string $columnName): string {
    return join(', ', $this->indexes);
  }

}