<?php

namespace Velis\Model\DataObject;

use InvalidArgumentException;
use Velis\App;
use Velis\Arrays;
use Velis\Exception;
use Velis\Label;
use Velis\Lang;
use Velis\Model\DataObject;
use Velis\User;

/**
 * Label mark functionality trait
 * @author Olek Procki <olo@velis.pl>
 */
trait LabelTrait
{
    /**
     * Object labels
     * @var Label[]
     */
    protected ?array $_labels = null;


    /**
     * @param DataObject[] $collection
     * @param User|int|null $user
     * @param bool $checkDq
     * @throws InvalidArgumentException
     * @throws NoColumnsException
     */
    public static function loadLabels($collection, $user = null, $checkDq = true)
    {
        $labelClass = App::$config->settings->labelClass ?: Label::class;

        if ($user == null) {
            $user = App::$user;
        }

        if (0 === count($collection)) {
            return;
        }
        $hasPrivLabelViewAll = $user->hasPriv('Admin', 'LabelViewAll');

        $params = [];

        if (!$hasPrivLabelViewAll) {
            $params['user'] = $user instanceof User ? $user->id() : $user;
        }

        $instance = reset($collection);
        $ownerUserField = $instance instanceof User ? 'added_by_user_id' : 'user_id';

        if (!($instance instanceof self)) {
            throw new InvalidArgumentException("Cannot load labels for different class objects");
        }

        $type = Label::getTypeForObject($instance);

        if (!$type) {
            return;
        }

        $query = "SELECT * FROM app.label_tab l JOIN app.label_{$type}_tab lo USING (label_id)";

        if (!is_array($instance->_getPrimaryKeyField())) {
            $entities = ['lo.' . $instance->_getPrimaryKeyField() => self::getCollectionIds($collection)];
            $query .= " WHERE 1=1" . self::$_db->conditions($entities);
            $params += $entities;
        } else {
            $query .= ' WHERE (lo.' . implode(',lo.', $instance->_getPrimaryKeyField()) . ') IN (';
            $queryParams = [];
            foreach (array_values($collection) as $key => $entity) {
                $element = [];
                foreach ($instance->_getPrimaryKeyField() as $keyField) {
                    $element[] = ":{$keyField}_{$key}";
                    $params["{$keyField}_{$key}"] = $entity[$keyField];
                }
                $queryParams[] = '(' . implode(',', $element) . ')';
            }
            $query .= implode(', ', $queryParams) . ')';
        }

        if (!$hasPrivLabelViewAll) {
            $query .= " AND (
                        (lo.{$ownerUserField}=:user OR lo.{$ownerUserField} IN (
                            SELECT
                                ua.user_id
                            FROM
                                acl.user_absence_tab ua
                            WHERE
                                ua.substitute_user_id = :user
                                AND now() >= ua.date_from
                                AND now() <= ua.date_to
                        ))";

            if (Label::hasField('label_type_id')) {
                $query .= " OR l.label_type_id = 'user'";
            }

            if (Label::hasField('company_id') && App::$user->hasCompany()) {
                $query .= " OR company_id=" . App::$user->getCompanyId();
            } elseif (App::$config->settings->allPublicLabelsAccess) {
                $query .= " OR l.is_public=1";
            } else {
                $query .= " OR (l.is_public=1 AND (l.user_id=:user OR EXISTS (
                            SELECT 1 FROM app.label_to_user_tab ltu
                            WHERE l.label_id = ltu.label_id
                                AND (ltu.user_id=:user OR ltu.user_id IN (
                                    SELECT
                                        ua.user_id
                                    FROM
                                        acl.user_absence_tab ua
                                    WHERE
                                        ua.substitute_user_id = :user
                                        AND now() >= ua.date_from
                                        AND now() <= ua.date_to
                                ))
                        )))";
            }

            if ($labelClass::getExtendedVisibilityConditions()) {
                $query .= " OR " . $labelClass::getExtendedVisibilityConditions();
            }

            $query .= ")";
        }

        if (App::$config->settings->showLabelOnlyInUserLanguage) {
            $query .= " AND (l.name)." . Lang::getLanguage() . " <> ''";
        }

        $restore = false;
        if (!$checkDq) {
            $restore = self::$_db->checkDuplicatedQueries(false);
        }

        $labels = [];
        foreach (self::$_db->getAll($query, $params) as $row) {
            $labels[] = new $labelClass($row);
        }

        self::$_db->checkDuplicatedQueries($restore);

        foreach ($collection as $elem) {
            if (!isset($elem->_labels)) {
                $elem->_labels = [];
            }
            if (!is_array($instance->_getPrimaryKeyField())) {
                $elem->_labels = Arrays::byValue($labels, $instance->_getPrimaryKeyField(), $elem->id());
            } else {
                foreach ($labels as $label) {
                    if ($elem->id() == Arrays::extractFields($label, $instance->_getPrimaryKeyField())) {
                        $elem->_labels[] = $label;
                    }
                }
            }
        }
    }


    /**
     * Returns object labels
     *
     * @param User $user
     * @return Label[]
     */
    public function getLabels($user = null)
    {
        if (!isset($this->_labels)) {
            if ($this->offsetExists('label_ids')) {
                $labelIds = $this->fieldToArray('label_ids');
                $labels = App::$user->getLabels();
            } else {
                $collection = array($this);
                static::loadLabels($collection, $user, false);

                $labels = $this->_labels;
                $userLabels = App::$user->getLabels();
                $labelIds = Arrays::getColumn($userLabels, 'label_id');
            }

            foreach ($labels as $key => $label) {
                $labelId = $label->id();
                if (!in_array($labelId, $labelIds)) {
                    unset($labels[$key]);
                }
            }
            $this->_labels = $labels;
        }

        natcasesort($this->_labels);
        return $this->_labels;
    }


    /**
     * Return label ids
     *
     * @param User $user
     * @return int[]
     * @throws Exception
     */
    public function getLabelIds($user = null)
    {
        $userLabels = $this->getLabels($user);

        $tmp = Arrays::getColumn($userLabels, 'label_id');

        return $tmp;
    }


    /**
     * Returns true if object is marked by $label
     *
     * @param Label|int $label
     * @return bool
     */
    public function hasLabel($label)
    {
        if (!($label instanceof Label)) {
            $label = new Label($label);
        }

        foreach ($this->getLabels() as $objectLabel) {
            if (!$objectLabel instanceof Label) {
                continue;
            }

            if ($objectLabel->id() == $label->id()) {
                return true;
            }
        }

        return false;
    }


    /**
     * Returns true if object has fav label
     *
     * @return bool
     */
    public function isFav()
    {
        return $this->hasLabel(Label::favLabelId());
    }


    /**
     * Returns label type for object
     * @return string
     */
    public function getLabelType()
    {
        return Label::getTypeForObject($this);
    }


    /**
     * Returns label identifier as string (for usage with HTML/JavaScript)
     * @return string
     */
    public function getLabelIdentifier()
    {
        $ident = 'label-' . $this->getLabelType();
        if (!is_array($this->_getPrimaryKeyField())) {
            return $ident . $this->id();
        } else {
            return $ident . md5(serialize($this->id()));
        }
    }
}
