<?php

namespace Booktics\Abstracts;

/**
 * @method array get_virtual_attributes( object $row ) Child classes may implement this to provide virtual attributes
 */
abstract class Model {
    protected $table;
    protected $fillable = array();

    /**
     * @var Booktics_Database
     */
    protected $db;

    /**
     * Constructor for BookticsModel
     *
     * @param Booktics_Database $db
     */
    public function __construct( Booktics_Database $db ) {
        $this->db = $db;
    }

    /**
     * Get a query builder instance for the model's table
     *
     * @return mixed
     */
    public function query() {
        return $this->db->table( $this->table );
    }

    /**
     * Apply conditions to a query builder instance
     *
     * @param mixed $query
     * @param array $conditions
     *
     * @return mixed
     */
    protected function apply_conditions( $query, array $conditions = array() ) {
        foreach ( $conditions as $field => $value ) {
            if ( is_array( $value ) ) {
                if ( strtolower( $value[0] ) === 'in' && is_array( $value[1] ) ) {
                    $query->where_in( $field, $value[1] );
                } else {
                    $query->where( $field, $value[0] ?? '=', $value[1] );
                }
            } else {
                $query->where( $field, '=', $value );
            }
        }

        return $query;
    }

    /**
     * Get total number of records based on conditions
     *
     * @param array $conditions
     *
     * @return int
     */
    public function count( array $conditions = array() ) {
        $query = $this->apply_conditions( $this->db->table( $this->table ), $conditions );

        return $query->count();
    }

    /**
     * Get sum of a field based on conditions
     *
     * @param string $field
     * @param array $conditions
     *
     * @return float
     */
    public function sum( string $field, array $conditions = array() ) {
        $query = $this->apply_conditions( $this->db->table( $this->table ), $conditions );

        return (float) $query->sum( $field );
    }

    /**
     * Create a new record in the database
     *
     * @param array $data
     *
     * @return mixed|false
     */
    public function create( array $data ) {
        $filtered = array_intersect_key( $data, array_flip( $this->fillable ) );
        $result   = $this->db->table( $this->table )->insert( $filtered );

        return $result > 0 ? $this->find( array( 'id' => $result ) ) : false;
    }

    /**
     * Update an existing record in the database
     *
     * @param int $id
     * @param array $data
     *
     * @return mixed
     */
    public function update( int $id, array $data ) {
        $filtered = array_intersect_key( $data, array_flip( $this->fillable ) );

        return $this->db->table( $this->table )->where( 'id', '=', $id )->update( $filtered );
    }

    /**
     * Delete a record from the database
     *
     * @param int $id
     *
     * @return mixed
     */
    public function delete( int $id ) {
        return $this->db->table( $this->table )->where( 'id', '=', $id )->delete();
    }

    /**
     * Get multiple records from the database with pagination
     *
     * @param array $conditions
     * @param int $page Current page number
     * @param int $per_page Items per page
     * @param array $order_by
     *
     * @return array
     */
    public function get( array $conditions = array(), int $page = 1, int $per_page = 10, $order_by = array() ) {
        $query  = $this->apply_conditions( $this->db->table( $this->table ), $conditions );
        $total  = $query->count();
        $offset = ( $page - 1 ) * $per_page;
        $data   = $query->limit( $per_page )->offset( $offset )
                        ->order_by( $order_by )->get();
        foreach ( $data as &$row ) {
            $row = $this->with_virtual_attributes( $row );
        }

        return array(
            'items'        => $data,
            'current_page' => $page,
            'per_page'     => $per_page,
            'total'        => $total,
            'total_pages'  => ceil( $total / $per_page ),
        );
    }

    /**
     * Find a single record from the database
     *
     * @param array $conditions
     *
     * @return mixed
     */
    public function find( array $conditions = array() ) {
        $query = $this->apply_conditions( $this->db->table( $this->table ), $conditions );
        $row   = $query->first();
        if ( is_wp_error( $row ) || $row === null ) {
            return $row;
        }

        return $this->with_virtual_attributes( $row );
    }

    /**
     * Get the table name for the model
     *
     * @return string
     */
    public function get_table_name() {
        return $this->table;
    }

    /**
     * Get the virtual attributes for the model
     *
     * @param object $row
     *
     * @return object
     */
    public function with_virtual_attributes( $row ) {
        if ( method_exists( $this, 'get_virtual_attributes' ) && $row ) {
            foreach ( $this->get_virtual_attributes( $row ) as $key => $value ) {
                $row->$key = $value;
            }
        }

        return $row;
    }
}
