<?php
/**
 * @author Daniel J. Summers <daniel@bitbadger.solutions>
 * @license MIT
 */

declare(strict_types=1);

namespace BitBadger\PDODocument;

use BitBadger\InspiredByFSharp\Option;
use Exception;
use PDO;

/**
 * Common configuration for the document library
 */
class Configuration
{
    /** @var string The name of the ID field used in the database (will be treated as the primary key) */
    public static string $idField = 'id';

    /** @var AutoId The automatic ID generation process to use */
    public static AutoId $autoId = AutoId::None;

    /**
     * @var int The number of characters a string generated by `AutoId::RandomString` will have (must be an even number)
     */
    public static int $idStringLength = 16;

    /** @var string|null The username to use to establish a data connection (use env PDO_DOC_USERNAME if possible) */
    public static ?string $username = null;

    /** @var string|null The password to use to establish a data connection (use env PDO_DOC_PASSWORD if possible) */
    public static ?string $password = null;

    /** @var mixed[]|null Options to use for connections (driver-specific) */
    public static ?array $options = null;

    /** @var Option<Mode> The mode in which the library is operating */
    public static Option $mode;

    /** @var Option<string> The data source name (DSN) of the connection string */
    private static Option $pdoDSN;

    /** @var PDO|null The PDO instance to use for database commands */
    private static ?PDO $pdo = null;

    /**
     * Use a Data Source Name (DSN)
     *
     * @param string $dsn The data source name to use (driver:[parameters])
     * @throws DocumentException If a DSN does not start with `pgsql:` or `sqlite:`
     */
    public static function useDSN(string $dsn): void
    {
        if (empty($dsn)) {
            self::$mode = self::$pdoDSN = Option::None();
        } else {
            self::$mode   = Option::Some(Mode::deriveFromDSN($dsn));
            self::$pdoDSN = Option::Some($dsn);
        }
    }

    /**
     * Retrieve a new connection to the database
     *
     * @return PDO A new connection to the SQLite database with foreign key support enabled
     * @throws Exception If this is called before a connection string is set
     */
    public static function dbConn(): PDO
    {
        if (is_null(self::$pdo)) {
            $dsn = self::$pdoDSN->getOrThrow(fn()
                => new DocumentException('Please provide a data source name (DSN) before attempting data access'));
            self::$pdo = new PDO($dsn, $_ENV['PDO_DOC_USERNAME'] ?? self::$username,
                $_ENV['PDO_DOC_PASSWORD'] ?? self::$password, self::$options);
        }

        return self::$pdo;
    }

    /**
     * Retrieve the mode for the current database connection
     *
     * @return Mode The mode for the current database connection
     * @throws Exception If the database mode has not been set
     */
    public static function mode(?string $process = null): Mode
    {
        return self::$mode->getOrThrow(fn()
            => new DocumentException('Database mode not set' . (is_null($process) ? '' : "; cannot $process")));
    }

    /**
     * You probably don't mean to be calling this; it is here for testing only
     *
     * @param Mode|null $mode The mode to set
     */
    public static function overrideMode(?Mode $mode): void
    {
        self::$mode = Option::of($mode);
    }

    /**
     * Clear the current PDO instance
     */
    public static function resetPDO(): void
    {
        self::$pdo = null;
    }
}