<?php

declare(strict_types=1);

namespace Drush\Sql;

use Drush\Utils\StringUtils;

/**
 * Note: when using this trait, also implement ConfigAwareInterface/ConfigAwareTrait.
 */
trait SqlTableSelectionTrait
{
    /**
     * Given a list of all tables, expand the convert the wildcards in the
     * option-provided lists into a list of actual table names.
     *
     * @param array $options An options array as passed to an Annotated Command.
     * @param array $all_tables A list of all eligible tables.
     *
     * @return array
     *   An array of tables with each table name in the appropriate
     *   element of the array.
     */
    public function getExpandedTableSelection(array $options, array $all_tables): array
    {
        $table_selection = $this->getTableSelection($options);
        // Get the existing table names in the specified database.
        if (isset($table_selection['skip'])) {
            $table_selection['skip'] = $this->expandAndFilterTables($table_selection['skip'], $all_tables);
        }
        if (isset($table_selection['structure'])) {
            $table_selection['structure'] = $this->expandAndFilterTables($table_selection['structure'], $all_tables);
        }
        if (isset($table_selection['tables'])) {
            $table_selection['tables'] = $this->expandAndFilterTables($table_selection['tables'], $all_tables);
        }
        return $table_selection;
    }

    /**
     * Given the table names in the input array that may contain wildcards (`*`),
     * expand the table names so that the array returned only contains table names
     * that exist in the database.
     *
     * @param $tables
     *   An array of table names where the table names may contain the
     *   `*` wildcard character.
     * @param $db_tables
     *   The list of tables present in a database.
     * @return array
     *   An array of tables with non-existent tables removed.
     */
    public function expandAndFilterTables(array $tables, array $db_tables): array
    {
        $expanded_tables = $this->ExpandWildcardTables($tables, $db_tables);
        $tables = $this->filterTables(array_merge($tables, $expanded_tables), $db_tables);
        $tables = array_unique($tables);
        sort($tables);
        return $tables;
    }

    /**
     * Expand wildcard tables.
     *
     * @param array $tables
     *   An array of table names, some of which may contain wildcards (`*`).
     * @param array $db_tables
     *   An array with all the existing table names in the current database.
     * @return array
     *   $tables array with wildcards resolved to real table names.
     */
    public function expandWildcardTables(array $tables, array $db_tables): array
    {
        // Table name expansion based on `*` wildcard.
        $expanded_db_tables = [];
        foreach ($tables as $k => $table) {
            // Only deal with table names containing a wildcard.
            if (str_contains($table, '*')) {
                $pattern = '/^' . str_replace('*', '.*', $table) . '$/i';
                // Merge those existing tables which match the pattern with the rest of
                // the expanded table names.
                $expanded_db_tables += preg_grep($pattern, $db_tables);
            }
        }
        return $expanded_db_tables;
    }

    /**
     * Filters tables.
     *
     * @param $tables
     *   An array of table names to filter.
     * @param $db_tables
     *   An array with all the existing table names in the current database.
     * @return array
     *   An array with only valid table names (i.e. all of which actually exist in
     *   the database).
     */
    public function filterTables(array $tables, array $db_tables): array
    {
        // Ensure all the tables actually exist in the database.
        foreach ($tables as $k => $table) {
            if (!in_array($table, $db_tables)) {
                unset($tables[$k]);
            }
        }

        return $tables;
    }

    /**
     * Construct an array that places table names in appropriate
     * buckets based on whether the table is to be skipped, included
     * for structure only, or have structure and data dumped.
     * The keys of the array are:
     * - skip: tables to be skipped completed in the dump
     * - structure: tables to only have their structure i.e. DDL dumped
     * - tables: tables to have structure and data dumped
     *
     * @return array
     *   An array of table names with each table name in the appropriate
     *   element of the array.
     */
    public function getTableSelection(array $options): array
    {
        // Skip large core tables if instructed.  Used by 'sql-drop/sql-dump/sql-sync' commands.
        $skip_tables = $this->getRawTableList('skip-tables', $options);
        // Skip any structure-tables as well.
        $structure_tables = $this->getRawTableList('structure-tables', $options);
        // Dump only the specified tables.  Takes precedence over skip-tables and structure-tables.
        $tables = $this->getRawTableList('tables', $options);

        return ['skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables];
    }

    /**
     * Consult the specified options and return the list of tables specified.
     *
     * @param $option_name
     *   The option name to check: skip-tables, structure-tables
     *   or tables.  This function will check both *-key and *-list.
     * @param $options An options array as passed to an Annotated Command.
     * @return array
     *   Returns an array of tables based on the first option
     *   found, or an empty array if there were no matches.
     */
    public function getRawTableList(string $option_name, array $options): array
    {
        $key_list = StringUtils::csvToArray($options[$option_name . '-key']);
        foreach ($key_list as $key) {
            $all_tables = $this->getConfig()->get('sql.' . $option_name, []);
            if (array_key_exists($key, $all_tables)) {
                return $all_tables[$key];
            }
            if ($option_name !== 'tables') {
                $all_tables = $this->getConfig()->get('sql.tables', []);
                if (array_key_exists($key, $all_tables)) {
                    return $all_tables[$key];
                }
            }
        }
        $table_list = $options[$option_name . '-list'];
        if (!empty($table_list)) {
            return StringUtils::csvToArray($table_list);
        }

        return [];
    }
}
