<?php

namespace GravityKit\GravityExport\Exporting;

use GravityKit\GravityExport\Feature;

/**
 * Removes all non-numeric columns that are empty.
 * @since $ver$
 */
final class RemoveEmptyColumns extends Feature {
	/**
	 * The name of the setting.
	 * @since $ver$
	 */
	private const SETTING_REMOVE_EMPTY_COLUMNS = 'remove-empty-columns';

	/**
	 * Keeps track of the empty columns.
	 * @since $ver$
	 */
	private $empty_columns = [];

	/**
	 * @inheritDoc
	 * @since $ver$
	 */
	protected function init(): void {
		add_filter( 'gfexcel_output_rows', \Closure::fromCallable( [ $this, 'remove_empty_rows' ] ), 10, 2 );
		add_filter( 'gfexcel_output_columns', \Closure::fromCallable( [ $this, 'remove_empty_columns' ] ), 10, 2 );
		add_filter( 'gk/gravityexport/settings/sections', \Closure::fromCallable( [ $this, 'add_settings' ] ), 10, 2 );
	}

	/**
	 * Keeps track of empty columns, and removes them from the rows.
	 * @since $ver$
	 *
	 * @param array $rows    The rows.
	 * @param int   $form_id The form id.
	 *
	 * @return array The filtered rows.
	 */
	private function remove_empty_rows( array $rows, int $form_id ): array {
		if ( ! $this->should_remove_empty_rows( $form_id ) ) {
			return $rows;
		}

		foreach ( $rows as $columns ) {
			if ( $this->get_empty_columns( $form_id ) === [] ) {
				// All columns are filled.
				break;
			}

			$this->update_empty_columns( $form_id, $columns );
		}

		$empty_columns = $this->get_empty_columns( $form_id );
		if ( ! $empty_columns ) {
			return $rows;
		}

		foreach ( $rows as $i => $columns ) {
			$rows[ $i ] = array_values( array_diff_key( $columns, $empty_columns ) );
		}

		return $rows;
	}

	/**
	 * Removes any empty columns from the columns list.
	 * @since $ver$
	 *
	 * @param array $columns The columns.
	 * @param int   $form_id The form id.
	 *
	 * @return array The filtered columns.
	 */
	private function remove_empty_columns( array $columns, int $form_id ): array {
		$empty_columns = $this->get_empty_columns( $form_id );
		if ( ! $empty_columns || ! $this->should_remove_empty_rows( $form_id ) ) {
			return $columns;
		}

		return array_values( array_diff_key( $columns, $empty_columns ) );
	}

	/**
	 * Whether the empty columns should be removed from the export.
	 * @since $ver$
	 *
	 * @param int $form_id The form id.
	 *
	 * @return bool
	 */
	private function should_remove_empty_rows( int $form_id ): bool {
		return gf_apply_filters(
			[ 'gk/gravityexport/feature/remove-empty-columns', $form_id ],
			(bool) $this->addon->get_plugin_setting( self::SETTING_REMOVE_EMPTY_COLUMNS ),
			$form_id
		);
	}

	/**
	 * Adds the settings for the appropriate section.
	 * @since $ver$
	 *
	 * @param array $sections The original sections.
	 *
	 * @return array The updated sections.
	 */
	private function add_settings( array $sections ): array {
		foreach ( $sections as $i => $section ) {
			if ( 'general-section' !== ( $section['id'] ?? null ) ) {
				continue;
			}

			$sections[ $i ]['settings'][] = [
				'title'       => esc_html__( 'Exclude empty columns', 'gk-gravityexport' ),
				'name'        => self::SETTING_REMOVE_EMPTY_COLUMNS,
				'id'          => self::SETTING_REMOVE_EMPTY_COLUMNS,
				'value'       => $this->addon->get_plugin_setting( self::SETTING_REMOVE_EMPTY_COLUMNS ),
				'description' => esc_html__( 'Any columns that do not contain values will be omitted from the export.', 'gk-gravityexport' ),
				'type'        => 'checkbox',
			];
		}

		return $sections;
	}

	/**
	 * Retrieves the empty columns.
	 * @since $ver$
	 *
	 * @param int $form_id The form id.
	 *
	 * @return array|null The empty columns.
	 */
	private function get_empty_columns( int $form_id ): ?array {
		return $this->empty_columns[ $form_id ] ?? null;
	}

	/**
	 * Updates the microcache for the empty columns.
	 * @since $ver$
	 *
	 * @param array $columns The columns from the row.
	 * @param int   $form_id The form id.
	 */
	private function update_empty_columns( int $form_id, array $columns ): void {
		$row = array_map( 'strval', $columns );

		$empty_columns = array_filter( $row, function ( $value ) {
			return ! is_numeric( $value ) && empty( $value );
		} );

		if ( $this->get_empty_columns( $form_id ) === null ) {
			$this->empty_columns[ $form_id ] = $empty_columns;
		}

		// Keep only the columns that are empty in all iterations.
		$this->empty_columns[ $form_id ] = array_intersect_key( $this->empty_columns[ $form_id ], $empty_columns );
	}
}
