<?php

namespace Wizard\MagicForger;

use Illuminate\Support\Collection;

class ConfigHelper
{
    // Config array
    public static $config = [];

    // Config file name constant
    public const CONFIG_FILE_NAME = 'mf_config.php';

    // Config path variable
    public static string $config_path;

    /**
     * Set up configuration path.
     */
    public static function setup_config_path(string $base_path): void
    {
        self::$config_path = $base_path.'/'.self::CONFIG_FILE_NAME;
    }

    /**
     * Get configuration path.
     */
    protected static function get_config_path(): string
    {
        return self::$config_path;
    }

    /**
     * Write configuration into a file.
     */
    public static function write_config(): void
    {
        $path = self::get_config_path();
        $str = '<?php
                        return '.self::varexport(self::$config, true).';';

        file_put_contents($path, $str);
        // After writing the file, format it
        self::format_file($path);
    }

    /**
     * Read configuration from a file.
     */
    public static function read_config(): void
    {
        $path = self::get_config_path();
        self::$config = include $path;
    }

    /**
     * Print configuration.
     */
    public static function print_config(): void
    {
        self::varexport(self::$config);
    }

    /**
     * PHP var_export() function with short array syntax (square brackets) indented 2 spaces.
     *
     * NOTE: The only issue is when a string value has `=>\n[`, it will get converted to `=> [`
     *
     * @see https://www.php.net/manual/en/function.var-export.php
     *
     * @param  bool  $return
     * @return string|string[]|null
     */
    public static function varexport(mixed $expression, $return = false): string|array|null
    {
        $export = var_export($expression, true);
        $patterns = [
            "/array \(/" => '[',
            "/^([ ]*)\)(,?)$/m" => '$1]$2',
            "/=>[ ]?\n[ ]+\[/" => '=> [',
            "/([ ]*)(\'[^\']+\') => ([\[\'])/" => '$1$2 => $3',
        ];
        $export = preg_replace(array_keys($patterns), array_values($patterns), $export);
        if ((bool) $return) {
            return $export;
        } else {
            echo $export;
        }

        return null;
    }

    /**
     * Format the given file.
     */
    protected static function format_file(string $path): void
    {
        exec('php-cs-fixer fix '.$path);
    }

    /**
     * Set up tables.
     */
    public static function set_up_tables(): Collection
    {
        $schema = \DB::connection()->getDoctrineSchemaManager();
        $tables = collect($schema->listTableNames())->all();
        $table_foreign_keys = [];
        foreach ($tables as $table) {
            $table_foreign_keys[$table] = $schema->listTableForeignKeys($table);
        }

        $insert_tables = [];
        foreach ($tables as $table) {
            $columns = [];
            $table_columns = $schema->listTableColumns($table);

            // Initiate new arrays for foreign keys
            $foreign_keys = [];
            $foreign_keys_reverse = [];

            // Check foreign key references from this table
            $foreign_keys_list = $table_foreign_keys[$table];
            foreach ($foreign_keys_list as $fk) {
                $foreign_keys[$fk->getLocalColumns()[0]] = [
                    'foreign_table' => $fk->getForeignTableName(),
                    'foreign_column' => $fk->getForeignColumns()[0],
                ];
            }

            foreach ($table_columns as $column) {
                $full_class = get_class($column->getType());
                $class_parts = explode('\\', $full_class);
                $class_name = end($class_parts);

                $columns[$column->getName()] = [
                    'type' => $class_name,
                    'should_insert' => [
                        'controller' => true,
                        'model' => true,
                        'requests' => true,
                        'views' => true,
                    ],
                ];
            }
            // Check foreign key references to this table
            foreach ($tables as $other_table) {
                if ($other_table != $table) {
                    $foreign_keys_list = $table_foreign_keys[$other_table];
                    foreach ($foreign_keys_list as $fk) {
                        if ($fk->getForeignTableName() == $table) {
                            $foreign_keys_reverse[] = [
                                'table' => $other_table,
                                'column' => $fk->getLocalColumns()[0],
                            ];
                        }
                    }
                }
            }
            $insert_tables[$table] = [];
            $insert_tables[$table]['columns'] = $columns;
            $insert_tables[$table]['foreign_keys'] = $foreign_keys; // Foreign keys FROM this table
            $insert_tables[$table]['foreign_keys_reverse'] = $foreign_keys_reverse; // Foreign keys TO this table
            $insert_tables[$table]['type'] = 'default';
        }

        self::merge_array_priority(self::$config['tables'], $insert_tables);

        return $tables;
    }

    /**
     * Merge two arrays and ensure priority values do not get overwritten.
     *
     * @param  array  $priority
     * @param  array  $merged
     */
    private static function merge_array_priority(&$priority, $merged): void
    {
        foreach ($merged as $key => $value) {
            // if the priority key is not set, automatically add the merged values
            if (! isset($priority[$key])) {
                $priority[$key] = $value;
            } else {
                // if the value is an array recursively merge with priority
                if (is_array($value) && is_array($priority[$key])) {
                    self::merge_array_priority($priority[$key], $value);
                }
            }
        }
    }
}
