<?php

declare(strict_types=1);

namespace app\models;

/**
 * Ребро
 */
abstract class Edge extends Document
{
    /**
     * Свойства
     */
    public function attributes(): array
    {
        return array_merge(
            parent::attributes(),
            [
                '_from',
                '_to',
                'type'
            ]
        );
    }

    /**
     * Метки свойств
     */
    public function attributeLabels(): array
    {
        return array_merge(
            parent::attributeLabels(),
            [
                '_from' => 'От кого',
                '_to' => 'К кому',
            ]
        );
    }

    /**
     * Правила
     */
    public function rules(): array
    {
        return array_merge(
            parent::rules(),
            [
                [
                    ['_from', '_to'],
                    'required',
                    'message' => 'Заполните поле: {attribute}'
                ],
                [
                    'type',
                    'string'
                ]
            ]
        );
    }

    /**
     * Перед сохранением
     *
     * @todo Подождать обновление от ебаного Yii2 и добавить
     * проверку типов передаваемых параметров
     */
    public function beforeSave($data): bool
    {
        if (parent::beforeSave($data)) {
            if ($this->isNewRecord) {
            }

            return true;
        }

        return false;
    }

    /**
     * Записать (с проверкой на существование)
     *
     * Создаст ребро только в том случае, если его аналога не существует
     *
     * @param string $_from Идентификатор отправителя (_id)
     * @param string $_from Идентификатор получетеля (_id)
     * @param string $type Дополнительное поле - тип взаимосвязи
     * @param string $data Дополнительные данные
     *
     * @todo
     * 1. Удалить $type и оставить только $data
     */
    public static function writeSafe(string $_from, string $_to, string $type = '', array $data = []): ?static
    {
        if ($edge = self::searchByVertex($_from, $_to, limit: 1)) {
            // Найдено в базе данных

            return $edge[0];
        }

        return self::write($_from, $_to, $type, $data);
    }

    /**
     * Записать
     *
     * @param string $_from Идентификатор отправителя (_id)
     * @param string $_from Идентификатор получетеля (_id)
     * @param string $type Дополнительное поле - тип взаимосвязи @deprecated
     * @param string $data Дополнительные данные
     *
     * @todo
     * 1. Удалить $type и оставить только $data
     */
    public static function write(string $_from, string $_to, string $type = '', array $data = []): ?static
    {
        // Инициализация
        $edge = new static;

        // Настройка
        $edge->_from = $_from;
        $edge->_to = $_to;
        if (isset($type)) $edge->type = $type;

        foreach ($data as $key => $value) {
            if (is_int($key)) {
                // Обычная запись

                $edge->{$value} = true;
            } else {
                // Ассоциативная запись

                $edge->{$key} = $value;
            }
        }

        return $edge->save() ? $edge : null;
    }

    /**
     * Поиск ребра по его вершинам
     *
     * @param string $_from Идентификатор исходящей вершины
     * @param string $_to Идентификатор входящей вершины
     * @param string|null $type deprecated
     * @param int $limit Ограничение по количеству
     * @param array|null $filter Фильтр (where())
     */
    public static function searchByVertex(string $_from, string $_to, string|null $type = null, int $limit = 1, array|null $filter = null): array|null
    {
        $query = self::find()->where([
            '_from' => $_from,
            '_to' => $_to
        ]);

        if (isset($type)) {
            $query->where(['type' => $type]);
        }

        if (isset($filter)) {
            $query->where($filter);
        }

        return $query->limit($limit)->all();
    }

    /**
     * Поиск рёбер по направлению
     */
    public static function searchByDirection(string $_from, string $direction = 'OUTBOUND', string $type = '', array $where = [], int $limit = 1, int $page = 1): static|array|null
    {
        if (str_contains($direction, 'OUTBOUND')) {
            // Исходящие рёбра

            if (isset($where)) {
                // Получен параметр $where

                // Реинициализация
                $where = array_merge($where + ['_from' => $_from]);
            } else {
                // Не получен параметр $where

                // Реинициализация
                $where = array_merge(['_from' => $_from, 'type' => $type]);
            }

            $query = static::find()->where($where);
        } else if (str_contains($direction, 'INBOUND')) {
            // Входящие рёбра

            if (isset($where)) {
                // Получен параметр $where

                // Реинициализация
                $where = array_merge($where + ['_to' => $_from]);
            } else {
                // Не получен параметр $where

                // Реинициализация
                $where = array_merge(['_to' => $_from, 'type' => $type]);
            }

            $query = static::find()->where($where);
        } else if (str_contains($direction, 'ANY')) {
            // Исходящие и входящие рёбра

            return static::searchByDirection(_from: $_from, direction: 'OUTBOUND', type: $type, where: $where, limit: $limit, page: $page) + static::searchByDirection(_from: $_from, direction: 'INBOUND', type: $type, where: $where, limit: $limit, page: $page);
        }

        if ($limit < 2) {
            // Одно ребро или меньше

            return $query->one();
        } else {
            // Несколько рёбер

            return $query->limit($limit)->offset($limit * ($page - 1))->all();
        }
    }
}
