<?php

namespace GemBoards\Entities;

use GemBoards\Classes\AbstractEntity;
use GemBoards\Classes\Datetime;
use GemBoards\Classes\Exception\GemboardsInvalidArgumentException;
use GemBoards\Collections\ColumnCollection;
use GemBoards\factories\BoardFactory;
use GemBoards\Utils\Helper;

class Board extends AbstractEntity {

	protected string $table       = 'gemboards_boards';
	protected string $object_type = 'board';

	protected array $data = [
		'project_id'   => 0,
		'folder_id'    => 0,
		'board_type'   => 'kanban',
		'board_status' => 'active',
		'title'        => '',
		'icon_color'   => null,
		'is_private'   => false,
		'owner_id'     => 0,
		'created_at'   => '',
		'updated_at'   => '',
		'settings'     => [
			'completed_column_id' => 0,
		],
	];

	protected array $columns     = [];
	protected bool $read_columns = false;

	public static function factory(): BoardFactory {
		return BoardFactory::init();
	}

	public function get_project_id( string $context = 'view' ): int {
		return (int) $this->get_prop( 'project_id', $context );
	}

	public function get_folder_id( string $context = 'view' ): int {
		return (int) $this->get_prop( 'folder_id', $context );
	}

	public function get_board_type( string $context = 'view' ): string {
		return $this->get_prop( 'board_type', $context );
	}

	public function get_board_status( string $context = 'view' ): string {
		return $this->get_prop( 'board_status', $context );
	}

	public function get_title( string $context = 'view' ): string {
		return $this->get_prop( 'title', $context );
	}

	public function get_icon_color( string $context = 'view' ): ?string {
		return $this->get_prop( 'icon_color', $context );
	}

	protected function get_is_private( string $context = 'view' ): bool {
		return Helper::string_to_bool( $this->get_prop( 'is_private', $context ) );
	}

	public function get_settings( string $context = 'view' ): array {
		return $this->get_prop( 'settings', $context );
	}

	public function get_board_settings( string $key = '', string $context = 'view' ) {
		$settings = $this->get_settings( $context );
		if ( empty( $key ) ) {
			return $settings;
		} else {
			return array_key_exists( $key, $settings ) ? $settings[ $key ] : null;
		}
	}

	public function get_completed_column_id(): int {
		return absint( $this->get_board_settings( 'completed_column_id' ) );
	}

	public function is_private( string $context = 'view' ): bool {
		return Helper::string_to_bool( $this->get_prop( 'is_private', $context ) );
	}

	public function get_owner_id( string $context = 'view' ): int {
		return (int) $this->get_prop( 'owner_id', $context );
	}

	/**
	 * @param bool $force_read
	 *
	 * @return Column[]
	 */
	public function get_columns( bool $force_read = false ): array {
		if ( ! $this->read_columns || $force_read ) {
			$this->read_columns();
		}

		return $this->columns;
	}

	protected function read_columns() {
		$this->columns = [];

		try {
			$columns            = new ColumnCollection( [
				'per_page' => - 1,
				'where'    => [
					[
						'key'   => 'board_id',
						'value' => $this->get_id(),
					],
				],
				'orderby'  => 'sort_order',
			] );
			$this->columns      = $columns->get_results();
			$this->read_columns = true;
		} catch ( GemboardsInvalidArgumentException $e ) {
			Helper::error_log( $e->getMessage() );
		}
	}


	/**
	 * @param string $context
	 *
	 * @return Datetime|null
	 */
	public function get_date_created_at( string $context = 'view' ) {
		return $this->get_prop( 'created_at', $context );
	}

	public function get_date_updated_at( string $context = 'view' ) {
		return $this->get_prop( 'updated_at', $context );
	}

	public function set_project_id( $value ) {
		$this->set_prop( 'project_id', absint( $value ) );
	}

	public function set_folder_id( $value ) {
		$this->set_prop( 'folder_id', absint( $value ) );
	}

	public function set_board_type( string $value ) {
		$this->set_prop( 'board_type', $value );
	}

	public function set_board_status( string $value ) {
		$this->set_prop( 'board_status', $value );
	}

	public function set_title( string $value ) {
		$this->set_prop( 'title', $value );
	}

	public function set_icon_color( ?string $value ) {
		$this->set_prop( 'icon_color', $value );
	}

	public function set_owner_id( ?int $value ) {
		$this->set_prop( 'owner_id', $value );
	}

	public function set_is_private( $value ) {
		$this->set_prop( 'is_private', Helper::string_to_bool( $value ) );
	}

	public function set_board_settings( string $key, $value ) {
		$settings         = $this->get_board_settings();
		$settings[ $key ] = $value;

		$this->set_prop( 'settings', $settings );
	}

	public function set_completed_column_id( int $column_id ) {
		$this->set_board_settings( 'completed_column_id', $column_id );
	}

	public function set_props( $props, string $context = 'set' ) {
		if ( array_key_exists( 'settings', $props ) && null === $props['settings'] ) {
			unset( $props['settings'] );
		} else {
			$props['settings'] = json_decode( $props['settings'], true );
		}

		return parent::set_props( $props, $context );
	}

	/**
	 * Methods are protected and final to keep this as an internal API.
	 *
	 * @param string $context
	 *
	 * @return array
	 * @internal
	 */
	protected function prepare_for_db( string $context = 'create' ): array {
		$data   = [];
		$format = [];

		$now = current_time( 'mysql', 1 );

		if ( 'create' === $context ) {
			if ( array_key_exists( 'created_at', $this->data ) ) {
				$this->set_date_prop( 'created_at', $now );
			}

			if ( array_key_exists( 'date_created', $this->data ) ) {
				$this->set_date_prop( 'date_created', $now );
			}
		}

		// Always set.
		if ( array_key_exists( 'updated_at', $this->data ) ) {
			$this->set_date_prop( 'updated_at', $now );
		}

		if ( array_key_exists( 'date_updated', $this->data ) ) {
			$this->set_date_prop( 'date_updated', $now );
		}

		// Should just get data from change when available and check context?
		$raw_data = $this->get_changes();

		if ( empty( $raw_data ) && 'create' === $context ) {
			$raw_data = $this->get_data(); // get defaults.
		}

		$extra_data_keys = $this->get_extra_data_keys();

		foreach ( array_keys( $this->data ) as $key ) {
			if ( 'update' === $context && ( str_contains( 'date_created', $key ) || str_contains( 'created_at', $key ) ) ) {
				continue;
			}

			if ( in_array( $key, $extra_data_keys, true ) ) {
				continue;
			}

			$getter = 'get_' . $key;
			if ( method_exists( $this, $getter ) ) {
				$value = $this->{$getter}( 'edit' );
			} else {
				$value = $raw_data[ $key ] ?? null;
			}

			if ( $value && is_a( $value, Datetime::class ) ) {
				$value = $this->prepare_date_for_db( $value );
			}

			if ( is_array( $value ) ) {
				$value = wp_json_encode( $value );
			}

			if ( is_object( $value ) ) {
				$value = maybe_serialize( $value );
			}

			$data[ $key ] = $value;
			$format[]     = $this->predict_format( $key, $value );
		}

		// Not using any filter for handling format.
		// If necessary update format for column in $wpdb::$field_types.
		return [
			'data'   => apply_filters( 'gemboards/' . $this->object_type . '/db/' . $context, $data, $this ),
			'format' => $format,
		];
	}

}
