<?php

namespace The7_Post_Types\Models;

use The7_Post_Types\Models\Items\Item_Model;

defined( 'ABSPATH' ) || exit;

abstract class Composition_Model {

	/**
	 * @param string $slug
	 *
	 * @return string
	 */
	public static function normalize_slug( $slug ) {
		return strtolower( sanitize_key( $slug ) );
	}

	/**
	 * @param string $item
	 *
	 * @return array|null
	 */
	public static function get( $item = null ) {
		$items = get_option( static::get_data_key(), [] );

		// Fix data in case of trouble.
		if ( ! is_array( $items ) ) {
			$items = [];
		}

		if ( $item ) {
			return isset( $items[ $item ] ) ? $items[ $item ] : null;
		}

		return $items;
	}

	/**
	 * @param array $data
	 *
	 * @return bool
	 */
	public static function save( $data ) {
		$data = array_map(
			function ( $item ) {
				if ( $item instanceof Item_Model ) {
					return $item->get_for_save();
				}

				return $item;
			},
			$data
		);

		return update_option( static::get_data_key(), array_filter( $data ) );
	}

	/**
	 * @param array $data
	 *
	 * @return bool
	 */
	public static function update( $data ) {
		$post_types = static::get();

		$post_types[ static::normalize_slug( $data['name'] ) ] = static::create_item( $data );

		return static::save( $post_types );
	}

	/**
	 * @param string $slug
	 *
	 * @return bool
	 */
	public static function delete( $slug ) {
		if ( ! $slug ) {
			return false;
		}

		$slug  = static::normalize_slug( $slug );
		$items = static::get();

		if ( array_key_exists( $slug, $items ) ) {
			unset( $items[ $slug ] );

			return static::save( $items );
		}

		return false;
	}

	/**
	 * @param string $original_slug
	 * @param string $new_slug
	 *
	 * @return int|false
	 */
	public static function replace_slug_in_content( $original_slug, $new_slug ) {
		global $wpdb;

		$original_slug = static::normalize_slug( $original_slug );
		$new_slug      = static::normalize_slug( $new_slug );

		if ( ! $original_slug || ! $new_slug || $original_slug === $new_slug ) {
			return false;
		}

		$quoted_old = '"' . $original_slug . '"';
		$quoted_new = '"' . $new_slug . '"';
		$path_old   = '/' . $original_slug . '/';
		$path_new   = '/' . $new_slug . '/';

		return $wpdb->query(
			$wpdb->prepare(
				"UPDATE {$wpdb->posts}
				 SET post_content = REPLACE( REPLACE( post_content, %s, %s ), %s, %s )
				 WHERE post_content LIKE %s OR post_content LIKE %s",
				$quoted_old,
				$quoted_new,
				$path_old,
				$path_new,
				'%' . $wpdb->esc_like( $quoted_old ) . '%',
				'%' . $wpdb->esc_like( $path_old ) . '%'
			)
		);
	}

	/**
	 * @param string   $original_slug
	 * @param string   $new_slug
	 * @param string[] $prefixes Template name prefixes to update, e.g. ['single-', 'archive-'] or ['taxonomy-'].
	 *
	 * @return void
	 */
	public static function replace_slug_in_template_names( $original_slug, $new_slug, $prefixes ) {
		global $wpdb;

		$original_slug = static::normalize_slug( $original_slug );
		$new_slug      = static::normalize_slug( $new_slug );

		if ( ! $original_slug || ! $new_slug || $original_slug === $new_slug || empty( $prefixes ) ) {
			return;
		}

		foreach ( $prefixes as $prefix ) {
			$old_name = $prefix . $original_slug;
			$new_name = $prefix . $new_slug;

			$wpdb->query(
				$wpdb->prepare(
					"UPDATE {$wpdb->posts}
					 SET post_name = REPLACE( post_name, %s, %s )
					 WHERE post_type IN ( 'wp_template', 'wp_template_part' )
					   AND ( post_name = %s OR post_name LIKE %s )",
					$old_name,
					$new_name,
					$old_name,
					$wpdb->esc_like( $old_name ) . '-%'
				)
			);
		}
	}

	/**
	 * @param string $taxonomy_slug
	 *
	 * @return Item_Model[]|null
	 */
	public static function get_for_display_objects( $taxonomy_slug = null ) {
		$taxonomies = array_map( function ( $data ) {
			return static::create_item( $data );
		}, static::get() );

		$predefined = static::get_bundle_definition();

		foreach ( $predefined as $name => $class ) {
			if ( ! isset( $taxonomies[ $name ] ) ) {
				$taxonomies[ $name ] = static::create_item( [ 'name' => $name ] );
			}
		}

		if ( $taxonomy_slug ) {
			return isset( $taxonomies[ $taxonomy_slug ] ) ? $taxonomies[ $taxonomy_slug ] : null;
		}

		return array_filter( $taxonomies );
	}

	/**
	 * @param string $taxonomy_slug
	 *
	 * @return array|null
	 */
	public static function get_for_display( $taxonomy_slug = null ) {
		$taxonomies = static::get_for_display_objects( $taxonomy_slug );
		if ( is_array( $taxonomies ) ) {
			return array_map( function ( $item ) {
				return $item->get_raw();
			}, $taxonomies );
		} elseif ( $taxonomies ) {
			return $taxonomies->get_raw();
		}

		return null;
	}

	/**
	 * @param string $post_type
	 *
	 * @return void
	 */
	public static function mass_detach( $post_type ) {
		// Empty taxonomies list works as detach.
		static::mass_attach( $post_type, [] );
	}

	/**
	 * @param string $post_type
	 * @param string[]|null $tax_list
	 *
	 * @return bool
	 */
	public static function mass_attach( $post_type, $tax_list = null ) {
		if ( ! $post_type || $tax_list === null ) {
			return false;
		}

		$taxonomies_for_display = static::get_for_display_objects();

		foreach ( $taxonomies_for_display as $name => $taxonomy ) {
			$taxonomy->detach( $post_type );

			if ( $tax_list && in_array( $name, (array) $tax_list, true ) ) {
				$taxonomy->attach( $post_type );
			}
		}

		return static::save( $taxonomies_for_display );
	}

	/**
	 * @param array|Item_Model $data
	 *
	 * @return Item_Model
	 */
	public static function create_item( $data ) {
		if ( $data instanceof Item_Model) {
			return $data;
		}

		if ( ! is_array( $data ) ) {
			$data = [];
		}

		if ( isset( $data['name'] ) ) {
			$slug   = $data['name'];
			$bundle = static::get_bundle_definition();

			if ( isset( $bundle[ $slug ] ) ) {
				return static::create_bundled_item( $data, $bundle[ $slug ] );
			}
		}

		return static::create_custom_item( $data );
	}

	/**
	 * @return array
	 */
	public static function get_bundle_definition() {
		return [];
	}

	/**
	 * @return string
	 */
	abstract protected static function get_data_key();

	/**
	 * @param array $data
	 *
	 * @return Item_Model
	 */
	abstract protected static function create_custom_item( $data );

	/**
	 * @param array $data
	 * @param string $class
	 *
	 * @return Item_Model
	 */
	abstract protected static function create_bundled_item( $data, $class = null );

}
