<?php

declare(strict_types=1);

/**
 * This file is part of CodeIgniter 4 framework.
 *
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace CodeIgniter\Database;

use CodeIgniter\CLI\CLI;
use Config\Database;
use Faker\Factory;
use Faker\Generator;
use InvalidArgumentException;

/**
 * Class Seeder
 */
class Seeder
{
    /**
     * The name of the database group to use.
     *
     * @var non-empty-string
     */
    protected $DBGroup;

    /**
     * Where we can find the Seed files.
     *
     * @var string
     */
    protected $seedPath;

    /**
     * An instance of the main Database configuration
     *
     * @var Database
     */
    protected $config;

    /**
     * Database Connection instance
     *
     * @var BaseConnection
     */
    protected $db;

    /**
     * Database Forge instance.
     *
     * @var Forge
     */
    protected $forge;

    /**
     * If true, will not display CLI messages.
     *
     * @var bool
     */
    protected $silent = false;

    /**
     * Faker Generator instance.
     *
     * @deprecated
     */
    private static ?Generator $faker = null;

    /**
     * Seeder constructor.
     */
    public function __construct(Database $config, ?BaseConnection $db = null)
    {
        $this->seedPath = $config->filesPath ?? APPPATH . 'Database/';

        if ($this->seedPath === '') {
            throw new InvalidArgumentException('Invalid filesPath set in the Config\Database.');
        }

        $this->seedPath = rtrim($this->seedPath, '\\/') . '/Seeds/';

        if (! is_dir($this->seedPath)) {
            throw new InvalidArgumentException('Unable to locate the seeds directory. Please check Config\Database::filesPath');
        }

        $this->config = &$config;

        $db ??= Database::connect($this->DBGroup);

        $this->db    = $db;
        $this->forge = Database::forge($this->DBGroup);
    }

    /**
     * Gets the Faker Generator instance.
     *
     * @deprecated
     */
    public static function faker(): ?Generator
    {
        if (self::$faker === null && class_exists(Factory::class)) {
            self::$faker = Factory::create();
        }

        return self::$faker;
    }

    /**
     * Loads the specified seeder and runs it.
     *
     * @throws InvalidArgumentException
     */
    public function call(string $class)
    {
        $class = trim($class);

        if ($class === '') {
            throw new InvalidArgumentException('No seeder was specified.');
        }

        if (! str_contains($class, '\\')) {
            $path = $this->seedPath . str_replace('.php', '', $class) . '.php';

            if (! is_file($path)) {
                throw new InvalidArgumentException('The specified seeder is not a valid file: ' . $path);
            }

            // Assume the class has the correct namespace
            // @codeCoverageIgnoreStart
            $class = APP_NAMESPACE . '\Database\Seeds\\' . $class;

            if (! class_exists($class, false)) {
                require_once $path;
            }
            // @codeCoverageIgnoreEnd
        }

        /** @var Seeder $seeder */
        $seeder = new $class($this->config);
        $seeder->setSilent($this->silent)->run();

        unset($seeder);

        if (is_cli() && ! $this->silent) {
            CLI::write("Seeded: {$class}", 'green');
        }
    }

    /**
     * Sets the location of the directory that seed files can be located in.
     *
     * @return $this
     */
    public function setPath(string $path)
    {
        $this->seedPath = rtrim($path, '\\/') . '/';

        return $this;
    }

    /**
     * Sets the silent treatment.
     *
     * @return $this
     */
    public function setSilent(bool $silent)
    {
        $this->silent = $silent;

        return $this;
    }

    /**
     * Run the database seeds. This is where the magic happens.
     *
     * Child classes must implement this method and take care
     * of inserting their data here.
     *
     * @return void
     *
     * @codeCoverageIgnore
     */
    public function run()
    {
    }
}
