<?php
/**
 * Shared WP-CLI output formatting helpers.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\WpCli;

/**
 * Formats command output as table, JSON, or CSV.
 */
final class CommandFormatter {
	/**
	 * Prints row output.
	 *
	 * @param array<int,array<string,mixed>> $rows Rows.
	 * @param string[]                       $fields Fields to print.
	 * @param array<string,mixed>            $assoc_args Associative arguments.
	 */
	public function formatRows( array $rows, array $fields, array $assoc_args ): void {
		$format = (string) ( $assoc_args['format'] ?? 'table' );

		if ( 'json' === $format ) {
			$this->jsonLine( $rows );
			return;
		}

		if ( 'csv' === $format ) {
			\WP_CLI::line( implode( ',', $fields ) );
			foreach ( $rows as $row ) {
				\WP_CLI::line( implode( ',', array_map( fn ( string $field ): string => $this->csvValue( $row[ $field ] ?? '' ), $fields ) ) );
			}
			return;
		}

		$this->table( $rows, $fields );
	}

	/**
	 * Prints associative output.
	 *
	 * @param array<string,mixed> $data Data.
	 * @param array<string,mixed> $assoc_args Associative arguments.
	 */
	public function formatAssoc( array $data, array $assoc_args ): void {
		$format = (string) ( $assoc_args['format'] ?? 'table' );

		if ( 'json' === $format ) {
			$this->jsonLine( $data );
			return;
		}

		$rows = array();
		foreach ( $data as $key => $value ) {
			$rows[] = array(
				'key'   => (string) $key,
				'value' => $this->displayValue( $value ),
			);
		}

		$this->formatRows( $rows, array( 'key', 'value' ), array( 'format' => $format ) );
	}

	/**
	 * Prints a compact table.
	 *
	 * @param array<int,array<string,mixed>> $rows Rows.
	 * @param string[]                       $fields Fields.
	 */
	private function table( array $rows, array $fields ): void {
		$widths = array_fill_keys( $fields, 0 );

		foreach ( $fields as $field ) {
			$widths[ $field ] = strlen( $field );
		}

		foreach ( $rows as $row ) {
			foreach ( $fields as $field ) {
				$widths[ $field ] = max( $widths[ $field ], strlen( $this->displayValue( $row[ $field ] ?? '' ) ) );
			}
		}

		\WP_CLI::line( $this->tableLine( array_combine( $fields, $fields ), $fields, $widths ) );
		foreach ( $rows as $row ) {
			\WP_CLI::line( $this->tableLine( $row, $fields, $widths ) );
		}
	}

	/**
	 * Formats one table row.
	 *
	 * @param array<string,mixed>|false $row Row.
	 * @param string[]                  $fields Fields.
	 * @param array<string,int>         $widths Field widths.
	 */
	private function tableLine( $row, array $fields, array $widths ): string {
		$parts = array();
		$row   = false === $row ? array() : $row;

		foreach ( $fields as $field ) {
			$parts[] = str_pad( $this->displayValue( $row[ $field ] ?? '' ), $widths[ $field ] );
		}

		return implode( '  ', $parts );
	}

	/**
	 * Prints JSON with unescaped slashes.
	 *
	 * @param mixed $data Data.
	 */
	private function jsonLine( $data ): void {
		\WP_CLI::line( (string) wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) );
	}

	/**
	 * Converts a value for display.
	 *
	 * @param mixed $value Value.
	 */
	private function displayValue( $value ): string {
		if ( is_bool( $value ) ) {
			return $value ? 'true' : 'false';
		}

		if ( is_scalar( $value ) || null === $value ) {
			return (string) $value;
		}

		return (string) wp_json_encode( $value, JSON_UNESCAPED_SLASHES );
	}

	/**
	 * Escapes a CSV value.
	 *
	 * @param mixed $value Value.
	 */
	private function csvValue( $value ): string {
		$value = str_replace( '"', '""', $this->displayValue( $value ) );

		return '"' . $value . '"';
	}
}
