<?php

namespace HelpGent\WP_CLI;

use HelpGent\cli\Colors;
use HelpGent\cli\Table;
use Iterator;
use HelpGent\Mustangostang\Spyc;
use HelpGent\WP_CLI;
/**
 * Output one or more items in a given format (e.g. table, JSON).
 */
class Formatter
{
    /**
     * How the items should be output.
     *
     * @var array
     */
    private $args;
    /**
     * Standard prefix for object fields.
     *
     * @var string
     */
    private $prefix;
    /**
     * @param array $assoc_args Output format arguments.
     * @param array $fields Fields to display of each item.
     * @param string|bool $prefix Check if fields have a standard prefix.
     * False indicates empty prefix.
     */
    public function __construct(&$assoc_args, $fields = null, $prefix = \false)
    {
        $format_args = ['format' => 'table', 'fields' => $fields, 'field' => null];
        foreach (['format', 'fields', 'field'] as $key) {
            if (isset($assoc_args[$key])) {
                $format_args[$key] = $assoc_args[$key];
                unset($assoc_args[$key]);
            }
        }
        if (!\is_array($format_args['fields'])) {
            $format_args['fields'] = \explode(',', $format_args['fields']);
        }
        $format_args['fields'] = \array_map('trim', $format_args['fields']);
        $this->args = $format_args;
        $this->prefix = $prefix;
    }
    /**
     * Magic getter for arguments.
     *
     * @param string $key
     * @return mixed
     */
    public function __get($key)
    {
        return $this->args[$key];
    }
    /**
     * Display multiple items according to the output arguments.
     *
     * @param array|Iterator $items The items to display.
     * @param bool|array      $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `format()` if items in the table are pre-colorized. Default false.
     */
    public function display_items($items, $ascii_pre_colorized = \false)
    {
        if ($this->args['field']) {
            $this->show_single_field($items, $this->args['field']);
        } else {
            if (\in_array($this->args['format'], ['csv', 'json', 'table'], \true)) {
                $item = \is_array($items) && !empty($items) ? \array_shift($items) : \false;
                if ($item && !empty($this->args['fields'])) {
                    foreach ($this->args['fields'] as &$field) {
                        $field = $this->find_item_key($item, $field);
                    }
                    \array_unshift($items, $item);
                }
            }
            if (\in_array($this->args['format'], ['table', 'csv'], \true)) {
                if ($items instanceof Iterator) {
                    $items = Utils\iterator_map($items, [$this, 'transform_item_values_to_json']);
                } else {
                    $items = \array_map([$this, 'transform_item_values_to_json'], $items);
                }
            }
            $this->format($items, $ascii_pre_colorized);
        }
    }
    /**
     * Display a single item according to the output arguments.
     *
     * @param mixed      $item
     * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_multiple_fields()` if the item in the table is pre-colorized. Default false.
     */
    public function display_item($item, $ascii_pre_colorized = \false)
    {
        if (isset($this->args['field'])) {
            $item = (object) $item;
            $key = $this->find_item_key($item, $this->args['field']);
            $value = $item->{$key};
            if (\in_array($this->args['format'], ['table', 'csv'], \true) && (\is_object($value) || \is_array($value))) {
                $value = \json_encode($value);
            }
            WP_CLI::print_value($value, ['format' => $this->args['format']]);
        } else {
            $this->show_multiple_fields($item, $this->args['format'], $ascii_pre_colorized);
        }
    }
    /**
     * Format items according to arguments.
     *
     * @param array      $items
     * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if items in the table are pre-colorized. Default false.
     */
    private function format($items, $ascii_pre_colorized = \false)
    {
        $fields = $this->args['fields'];
        switch ($this->args['format']) {
            case 'count':
                if (!\is_array($items)) {
                    $items = \iterator_to_array($items);
                }
                echo \count($items);
                break;
            case 'ids':
                if (!\is_array($items)) {
                    $items = \iterator_to_array($items);
                }
                echo \implode(' ', $items);
                break;
            case 'table':
                self::show_table($items, $fields, $ascii_pre_colorized);
                break;
            case 'csv':
                Utils\write_csv(\STDOUT, $items, $fields);
                break;
            case 'json':
            case 'yaml':
                $out = [];
                foreach ($items as $item) {
                    $out[] = Utils\pick_fields($item, $fields);
                }
                if ('json' === $this->args['format']) {
                    if (\defined('JSON_PARTIAL_OUTPUT_ON_ERROR')) {
                        // phpcs:ignore PHPCompatibility.Constants.NewConstants.json_partial_output_on_errorFound
                        echo \json_encode($out, \JSON_PARTIAL_OUTPUT_ON_ERROR);
                    } else {
                        echo \json_encode($out);
                    }
                } elseif ('yaml' === $this->args['format']) {
                    echo Spyc::YAMLDump($out, 2, 0);
                }
                break;
            default:
                WP_CLI::error('Invalid format: ' . $this->args['format']);
        }
    }
    /**
     * Show a single field from a list of items.
     *
     * @param array $items Array of objects to show fields from
     * @param string $field The field to show
     */
    private function show_single_field($items, $field)
    {
        $key = null;
        $values = [];
        foreach ($items as $item) {
            $item = (object) $item;
            if (null === $key) {
                $key = $this->find_item_key($item, $field);
            }
            if ('json' === $this->args['format']) {
                $values[] = $item->{$key};
            } else {
                WP_CLI::print_value($item->{$key}, ['format' => $this->args['format']]);
            }
        }
        if ('json' === $this->args['format']) {
            echo \json_encode($values);
        }
    }
    /**
     * Find an object's key.
     * If $prefix is set, a key with that prefix will be prioritized.
     *
     * @param object $item
     * @param string $field
     * @return string
     */
    private function find_item_key($item, $field)
    {
        foreach ([$field, $this->prefix . '_' . $field] as $maybe_key) {
            if (\is_object($item) && (\property_exists($item, $maybe_key) || isset($item->{$maybe_key})) || \is_array($item) && \array_key_exists($maybe_key, $item)) {
                $key = $maybe_key;
                break;
            }
        }
        if (!isset($key)) {
            WP_CLI::error("Invalid field: {$field}.");
        }
        return $key;
    }
    /**
     * Show multiple fields of an object.
     *
     * @param object|array $data                Data to display
     * @param string       $format              Format to display the data in
     * @param bool|array   $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if the item in the table is pre-colorized. Default false.
     */
    private function show_multiple_fields($data, $format, $ascii_pre_colorized = \false)
    {
        $true_fields = [];
        foreach ($this->args['fields'] as $field) {
            $true_fields[] = $this->find_item_key($data, $field);
        }
        foreach ($data as $key => $value) {
            if (!\in_array($key, $true_fields, \true)) {
                if (\is_array($data)) {
                    unset($data[$key]);
                } elseif (\is_object($data)) {
                    unset($data->{$key});
                }
            }
        }
        switch ($format) {
            case 'table':
            case 'csv':
                $rows = $this->assoc_array_to_rows($data);
                $fields = ['Field', 'Value'];
                if ('table' === $format) {
                    self::show_table($rows, $fields, $ascii_pre_colorized);
                } elseif ('csv' === $format) {
                    Utils\write_csv(\STDOUT, $rows, $fields);
                }
                break;
            case 'yaml':
            case 'json':
                WP_CLI::print_value($data, ['format' => $format]);
                break;
            default:
                WP_CLI::error('Invalid format: ' . $format);
                break;
        }
    }
    /**
     * Show items in a \cli\Table.
     *
     * @param array      $items
     * @param array      $fields
     * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `Table::setAsciiPreColorized()` if items in the table are pre-colorized. Default false.
     */
    private static function show_table($items, $fields, $ascii_pre_colorized = \false)
    {
        $table = new Table();
        $enabled = WP_CLI::get_runner()->in_color();
        if ($enabled) {
            Colors::disable(\true);
        }
        $table->setAsciiPreColorized($ascii_pre_colorized);
        $table->setHeaders($fields);
        foreach ($items as $item) {
            $table->addRow(\array_values(Utils\pick_fields($item, $fields)));
        }
        foreach ($table->getDisplayLines() as $line) {
            WP_CLI::line($line);
        }
        if ($enabled) {
            Colors::enable(\true);
        }
    }
    /**
     * Format an associative array as a table.
     *
     * @param array     $fields    Fields and values to format
     * @return array
     */
    private function assoc_array_to_rows($fields)
    {
        $rows = [];
        foreach ($fields as $field => $value) {
            if (!\is_string($value)) {
                $value = \json_encode($value);
            }
            $rows[] = (object) ['Field' => $field, 'Value' => $value];
        }
        return $rows;
    }
    /**
     * Transforms objects and arrays to JSON as necessary
     *
     * @param mixed $item
     * @return mixed
     */
    public function transform_item_values_to_json($item)
    {
        foreach ($this->args['fields'] as $field) {
            $true_field = $this->find_item_key($item, $field);
            $value = \is_object($item) ? $item->{$true_field} : $item[$true_field];
            if (\is_array($value) || \is_object($value)) {
                if (\is_object($item)) {
                    $item->{$true_field} = \json_encode($value);
                } elseif (\is_array($item)) {
                    $item[$true_field] = \json_encode($value);
                }
            }
        }
        return $item;
    }
}
