<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Relations\Relation;
use ReflectionClass;
use ReflectionMethod;

trait GetRelationshipsTraits
{
    /**
     * Auditable boot logic.
     */
    public static function bootGetRelationshipsTraits(): void
    {
        static::setAvailableRelations();
    }

    protected static $excludedTraitFromReflect = [
        __TRAIT__,
        'OwenIt\Auditing\Auditable',
    ];

    protected static $excludeTraitMethodeFromGetRelationships = [];

    public static function getRelationships(): array
    {
        $model = new static;
        $relationships = [];

        $excludedMethods = [
            'getRelationships',
            'getRelationshipsNames',
            'setAvailableRelations',
            'resolveUser',
            'resolveUrl',
            'resolveIpAddress',
            'resolveUserAgent',
            'toAudit',
            'transformAudit',
            'modifyAttributeValue',
            'readyForAuditing',
            'getAuditEvents',
            'getAuditInclude',
            'getAuditExclude',
            'getAuditStrict',
            'getAuditTimestamps',
            'getAuditDriver',
            'getAuditThreshold',
            'getAttributeModifiers',
            'generateTags',
            'transitionTo',
        ];

        foreach ((new ReflectionClass($model))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            if ($method->class != get_class($model) ||
                ! empty($method->getParameters()) ||
                in_array($method->getName(), $excludedMethods) ||
                str_starts_with($method->getName(), 'boot')
            ) {
                continue;
            }

            try {
                $return = $method->invoke($model);

                if ($return instanceof Relation) {
                    $relationships[$method->getName()] = [
                        'type' => (new ReflectionClass($return))->getShortName(),
                        'model' => (new ReflectionClass($return->getRelated()))->getName(),
                    ];
                }
            } catch (\Throwable $e) {
            }
        }

        return $relationships;
    }

    /**
     * Available relationships for the model.
     *
     * @var array
     */
    public static $availableRelations = [];

    public static function getRelationshipsNames($needInitialize = false, $outputFormat = 'camelCase'): array
    {
        if ($needInitialize) {
            self::setAvailableRelations();
        }
        $ret = array_keys(self::$availableRelations);
        switch ($outputFormat) {
            case 'camelCase':
                $ret = array_map(function ($val) {
                    return lcfirst($val);
                }, $ret);
                break;
            case 'snakeCase':
            case 'snake_case':
                $ret = array_map(function ($val) {
                    return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $val));
                }, $ret);
                break;
            case 'pascalCase':
            case 'PascalCase':
                // do nothing
                break;
            case 'kebabCase':
            case 'kebab-case':
                $ret = array_map(function ($val) {
                    return strtolower(preg_replace('/(?<!^)[A-Z]/', '-$0', $val));
                }, $ret);
                break;
            default:

                break;
        }

        return $ret;
    }

    /**
     * Stores relationships for future use
     */
    public static function setAvailableRelations(?array $relations = null): array
    {
        if (! $relations) {
            $relations = self::getRelationships();
        }
        self::$availableRelations = $relations;

        return $relations;
    }
}
