<?php

namespace classes;

class object_property
{
    public int $id; // The table of the objects in the database (e.g. users)
    private string $table; // The id of the object in the database
    private string $column; // The column name of the field in the database table (e.g. id, name, email)
    private string $type; // The data type of the field in the database table (e.g. int, varchar, text)
    private bool $required; // Whether the field is required or not
    private mixed $default; // The default value of the field
    private mixed $fake_value; // The fake value of the field, used for testing purposes (When the object id is -1)

    public function __construct(string $table, int $id, string $column, string $type, bool $required = false, mixed $default = null)
    {
        $this->table = $table;
        $this->id = $id;
        $this->column = $column;
        $this->type = $type;
        $this->required = $required;
        $this->default = $default;
    }

    public function __toString(): string
    {
        // Return the value of the field in the database table
        return $this->value();
    }

    public function requireSelected(): void
    {
        // Check if the object is selected
        // This is a placeholder function. In a real implementation, this would check if the object
        if (!isset($this->id)) {
            throw new \RuntimeException("Object not selected");
        }
    }

    /**
     * Get the value of the field in the database table
     * @return mixed The value of the field in the database table
     */
    public function value(): mixed
    {
        self::requireSelected();
        if ($this->id < 0) {
            // If the object id is less than 0, return the fake value
            return $this->fake_value ?? $this->default;
        }
        // Get the value of the field in the database table
        global $db;
        // Sanitize the id to prevent SQL injection
        $this->id = (int)$this->id;
        // Check if the id is valid
        if ($this->id <= 0) {
            throw new \InvalidArgumentException("Invalid ID: $this->id");
        }
        // Check if the table and column are valid
        if (empty($this->table) || empty($this->column)) {
            throw new \InvalidArgumentException("Table or column cannot be empty");
        }
        // Check if the type is valid
        if (!in_array($this->type, ['int', 'varchar', 'text', 'date', 'datetime', 'string', 'timestamp', 'boolean', 'bool', 'float', 'json'])) {
            throw new \InvalidArgumentException("Invalid type: $this->type");
        }
        $sql = "SELECT $this->column FROM $this->table WHERE id = $this->id";
        $result = $db->query($sql);
        $row = $db->fetch_assoc($result);
        if (empty($row)) {
            throw new \RuntimeException("No record found for column '$this->column' in table '$this->table' with ID $this->id");
        }
        // If the type is set to json, decode it.
        $value = $row[$this->column];
        if ($this->type === 'json' && $value !== null) {
            return json_decode($value, true);
        }
        return $row[$this->column];
    }

    /**
     * Set the value of the field in the database table
     * @param mixed $value The value of the field in the database table
     */
    public function set(mixed $value): void
    {
        if ($this->id < 0) {
            // If the object id is less than 0, set the fake value
            $this->fake_value = $value;
            return;
        }
        // Set the value of the field in the database table
        global /** @var db $db */
        $db;

        // If the value is null, set it to null
        if ($value === null) {
            $sql = "UPDATE $this->table SET $this->column = NULL WHERE id = $this->id";
        }  // If the value is an integer, set it to the integer value
        elseif (is_int($value)) {
            $sql = "UPDATE $this->table SET $this->column = $value WHERE id = $this->id";
        }  // If the value is a float, set it to the float value
        elseif (is_float($value)) {
            $sql = "UPDATE $this->table SET $this->column = $value WHERE id = $this->id";
        }  // If the value is a boolean, store as 1/0
        elseif (is_bool($value)) {
            $sql = "UPDATE $this->table SET $this->column = " . ($value ? 1 : 0) . " WHERE id = $this->id";
        }  // If the column type is json, JSON-encode the value
        elseif ($this->type === 'json') {
            $encoded = json_encode($value, JSON_UNESCAPED_UNICODE);
            $encoded = $db->escape_string($encoded);
            $sql = "UPDATE $this->table SET $this->column = '$encoded' WHERE id = $this->id";
        }  // If the value is a string, escape it
        elseif (is_string($value)) {
            $value = $db->escape_string($value);
            $sql = "UPDATE $this->table SET $this->column = '$value' WHERE id = $this->id";
        } else {
            // Unsupported type for non-json columns
            $type = gettype($value);
            throw new \InvalidArgumentException("Unsupported value type '$type' for {$this->table}.{$this->column} (expected scalar or json-capable)");
        }
        $db->query($sql);
    }

    /**
     * Alias for set()
     * @param mixed $value The value of the field in the database table
     */
    public function update(mixed $value): void
    {
        $this->set($value);
    }

    /**
     * Nullify the value of the field in the database table
     */
    public function nullify(): void
    {
        if ($this->id < 0) {
            // If the object id is less than 0, set the fake value to null
            $this->fake_value = null;
            return;
        }
        // Set the value of the field in the database table to null
        global /** @var db $db */
        $db;
        $sql = "UPDATE $this->table SET $this->column = NULL WHERE id = $this->id";
        $db->query($sql);
    }
}