<?php
// components/Assistant/Flow.php
namespace Components\Assistant;

use Symfony\Component\Yaml\Yaml;
use Components\Common\FieldVisibility;
use Components\Form\Validation;

class Flow
{
    private array $cfg;

    public function __construct(string $yamlPath)
    {
        if (!is_file($yamlPath)) {
            throw new \RuntimeException("Flow YAML not found: $yamlPath");
        }
        $raw = Yaml::parseFile($yamlPath) ?? [];

        // If it's already in assistant flow format (has groups), use as-is
        if (isset($raw['groups']) && is_array($raw['groups'])) {
            $this->cfg = $raw;
        } else {
            // Try to interpret as blueprint.yaml (form/fields structure)
            if (isset($raw['form']['fields']) && is_array($raw['form']['fields'])) {
                $this->cfg = $this->fromBlueprint($raw);
            } else {
                throw new \RuntimeException('Invalid flow: missing groups or blueprint form.fields');
            }
        }
    }

    public function config(): array { return $this->cfg; }

    public function firstStepId(): string
    {
        foreach ($this->cfg['groups'] as $g) {
            if (!empty($g['steps'][0]['id'])) return (string)$g['steps'][0]['id'];
        }
        throw new \RuntimeException('No steps defined');
    }

    public function stepById(string $id): ?array
    {
        foreach ($this->cfg['groups'] as $g) {
            foreach ($g['steps'] as $s) {
                if (($s['id'] ?? '') === $id) {
                    $s['group'] = $g['id'] ?? null;
                    if (!empty($g['key'])) { $s['group_key'] = (string)$g['key']; }
                    return $s;
                }
            }
        }
        return null;
    }

    public function nextStepId(array $answers, array $step): ?string
    {
        $next = $step['next'] ?? null;
        if (!$next) return $this->cfg['end'] ?? null;

        if (is_string($next)) return $next;
        if (is_array($next) && !empty($next['when'])) {
            foreach ($next['when'] as $rule) {
                if ($this->matchCondition($answers, (string)($rule['if'] ?? ''))) {
                    return (string)($rule['go_to'] ?? null);
                }
            }
            return $next['else'] ?? ($this->cfg['end'] ?? null);
        }
        return $this->cfg['end'] ?? null;
    }

    private function matchCondition(array $answers, string $expr): bool
    {
        // Simple evaluator: "itemId == value"
        if (!preg_match('/^\s*([a-zA-Z0-9_]+)\s*==\s*([a-zA-Z0-9_\-]+)\s*$/', $expr, $m)) return false;
        [, $key, $value] = $m;
        $given = $answers[$key] ?? null;
        if (is_array($given)) return in_array($value, $given, true);
        return (string)$given === $value;
    }

    public function mapItemToStep(): array
    {
        static $map = null;
        if ($map !== null) return $map;
        $map = [];
        foreach ($this->cfg['groups'] as $g) {
            foreach ($g['steps'] as $s) {
                foreach (($s['items'] ?? []) as $it) {
                    $map[$it['id']] = $s['id'];
                }
            }
        }
        return $map;
    }

    private function fromBlueprint(array $bp): array
    {
        $title = is_string($bp['title'] ?? null) ? $bp['title'] : 'Configuration';
        $fields = $bp['form']['fields'] ?? [];
        $groups = is_array($bp['form']['groups'] ?? null) ? $bp['form']['groups'] : [];

        // If groups are defined in blueprint, build grouped assistant flow preserving order
        if (!empty($groups)) {
            // Build a quick lookup for ungrouped detection
            $groupFieldSet = [];
            foreach ($groups as $g) {
                foreach ((array)($g['fields'] ?? []) as $fn) { $groupFieldSet[(string)$fn] = true; }
            }
            // Collect ungrouped field ids in original fields order
            $ungrouped = [];
            foreach ($fields as $fid => $_) {
                $fid = (string)$fid;
                if (!isset($groupFieldSet[$fid])) { $ungrouped[] = $fid; }
            }

            $assistantGroups = [];
            $stepIds = [];
            // Map each blueprint group to an Assistant group with one configure step
            foreach ($groups as $g) {
                if (!is_array($g)) continue;
                $gid = (string)($g['id'] ?? '');
                $glabel = (string)($g['label'] ?? $g['title'] ?? $gid);
                if ($gid === '') { $gid = $this->slugify($glabel ?: 'group'); }
                $fieldNames = [];
                foreach ((array)($g['fields'] ?? []) as $fn) {
                    $name = (string)$fn;
                    if ($name !== '' && isset($fields[$name]) && is_array($fields[$name])) {
                        $fieldNames[] = $name;
                    }
                }
                // Map fields to items
                $items = [];
                foreach ($fieldNames as $name) {
                    $items[] = $this->mapBlueprintFieldToItem($name, (array)$fields[$name], $fields);
                }
                $items = array_values(array_filter($items));
                $stepId = $gid . '_configure';
                $stepIds[] = $stepId;
                $assistantGroups[] = [
                    'id' => $gid,
                    'key' => (string)($g['key'] ?? ''),
                    'title' => $glabel ?: $gid,
                    'steps' => [
                        [
                            'id' => $stepId,
                            'title' => $glabel ?: $gid,
                            'items' => $items,
                            // next will be linked after we gather all steps
                        ],
                    ],
                ];
            }
            // If there are ungrouped fields, append as an extra group "Other"
            if (!empty($ungrouped)) {
                $items = [];
                foreach ($ungrouped as $name) {
                    $def = (array)$fields[$name];
                    $items[] = $this->mapBlueprintFieldToItem($name, $def, $fields);
                }
                $items = array_values(array_filter($items));
                if (!empty($items)) {
                    $gid = 'other';
                    $glabel = 'Other';
                    $stepId = $gid . '_configure';
                    $stepIds[] = $stepId;
                    $assistantGroups[] = [
                        'id' => $gid,
                        'title' => $glabel,
                        'steps' => [
                            [
                                'id' => $stepId,
                                'title' => $glabel,
                                'items' => $items,
                            ],
                        ],
                    ];
                }
            }
            // Link step next pointers and add a final review step to the last group
            $reviewId = 'review';
            for ($i = 0; $i < count($assistantGroups); $i++) {
                $isLastGroup = ($i === count($assistantGroups) - 1);
                $step =& $assistantGroups[$i]['steps'][0];
                $step['next'] = $isLastGroup ? $reviewId : $assistantGroups[$i+1]['steps'][0]['id'];
                unset($step); // break reference
            }
            if (!empty($assistantGroups)) {
                // Append review step to last group
                $lastIdx = count($assistantGroups) - 1;
                $assistantGroups[$lastIdx]['steps'][] = [
                    'id' => $reviewId,
                    'title' => 'Review your choices',
                    'type' => 'review',
                    'enhance' => ['suggestions' => true],
                    'next' => 'done',
                ];
            }

            return [
                'version' => 1,
                'id' => $this->slugify($title) . '_assistant',
                'meta' => [
                    'title' => $title,
                    'description' => 'Assistant-generated from blueprint',
                ],
                'groups' => $assistantGroups,
                'end' => 'done',
            ];
        }

        // Otherwise: fallback to minimal one-step assistant flow
        // Preserve original order as defined in YAML by iterating keys
        $items = [];
        foreach ($fields as $fid => $fdef) {
            $fid = (string)$fid;
            if (!is_array($fdef)) continue;
            $items[] = $this->mapBlueprintFieldToItem($fid, $fdef, $fields);
        }
        $items = array_values(array_filter($items));

        $flow = [
            'version' => 1,
            'id' => $this->slugify($title) . '_assistant',
            'meta' => [
                'title' => $title,
                'description' => 'Assistant-generated from blueprint',
            ],
            'no_grouping' => true,
            'groups' => [
                [
                    'id' => 'generated',
                    'title' => $title,
                    'steps' => [
                        [
                            'id' => 'configure',
                            'title' => 'Configure ' . $title,
                            'description' => 'Answer a few questions to set up ' . $title,
                            'items' => $items,
                            'next' => 'review',
                        ],
                        [
                            'id' => 'review',
                            'title' => 'Review your choices',
                            'type' => 'review',
                            'enhance' => ['suggestions' => true],
                            'next' => 'done',
                        ],
                    ],
                ],
            ],
            'end' => 'done',
        ];
        return $flow;
    }

    private function mapBlueprintFieldToItem(string $id, array $f, array $allFields = []): ?array
    {
        $type = (string)($f['type'] ?? 'text');
        $label = $this->humanize($id);

        // Determine required
        $req = $f['validate']['required'] ?? $f['required'] ?? false;
        $required = filter_var($req, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
        if ($required === null) {
            // Handle common string representations like '1', 'true', ''
            $required = in_array((string)$req, ['1','true','yes'], true);
        }

        // Collect validation (normalized to client schema: includes type/min/max/compare)
        $validate = Validation::toClientSchema([$id => $f])[$id] ?? [];

        // Single/multi choice detection
        $values = $f['values'] ?? null;
        $hasValues = is_array($values) && count($values) > 0;
        $validateType = (string)($f['validate']['type'] ?? '');

        // Prepare visible_if (transformed for Assistant mapping where needed)
        $visibleIf = null;
        if (!empty($f['visible_if']) && is_array($f['visible_if'])) {
            $tmp = $f['visible_if'];
            $ctrl = (string)($tmp['field'] ?? '');
            if ($ctrl !== '' && isset($allFields[$ctrl])) {
                $cdef = (array)$allFields[$ctrl];
                $ctype = strtolower((string)($cdef['type'] ?? ''));
                $cval = strtolower((string)($cdef['validate']['type'] ?? ''));
                // If controller is a toggle/boolean, assistant maps it to yes/no; transform conditions accordingly
                $toYesNo = ($ctype === 'toggle' || $cval === 'boolean');
                if ($toYesNo) {
                    $mapVal = function($v) {
                        $s = strtolower((string)$v);
                        if ($s === 'on' || $s === '1' || $s === 'true' || $s === 'yes') return 'yes';
                        if ($s === '' || $s === '0' || $s === 'false' || $s === 'no' || $s === 'off') return 'no';
                        return $s;
                    };
                    if (isset($tmp['eq'])) { $tmp['eq'] = $mapVal($tmp['eq']); }
                    if (isset($tmp['any_of']) && is_array($tmp['any_of'])) { $tmp['any_of'] = array_map($mapVal, $tmp['any_of']); }
                    if (isset($tmp['in']) && is_array($tmp['in'])) { $tmp['in'] = array_map($mapVal, $tmp['in']); }
                }
            }
            $visibleIf = $tmp;
        }

        $item = null;

        // Map known types
        if ($type === 'toggle' || $validateType === 'boolean') {
            $item = [
                'id' => $id,
                'type' => 'single_choice',
                'label' => $label,
                'options' => [
                    ['id' => 'yes', 'label' => 'Yes'],
                    ['id' => 'no', 'label' => 'No'],
                ],
                'required' => (bool)$required,
            ];
        } elseif ($validateType === 'select' && $hasValues) {
            $item = [
                'id' => $id,
                'type' => 'single_choice',
                'label' => $label,
                'options' => $this->mapValuesToOptions($values),
                'required' => (bool)$required,
            ];
        } elseif ((is_string($type) && strpos($type, 'multiple') !== false) || $validateType === 'checkbox') {
            // Checkbox or multi-select types
            if ($hasValues) {
                $item = [
                    'id' => $id,
                    'type' => 'multi_choice',
                    'label' => $label,
                    'options' => $this->mapValuesToOptions($values),
                    'required' => (bool)$required,
                ];
            } else {
                // No options available -> fall back to free text
                $item = [
                    'id' => $id,
                    'type' => 'text',
                    'label' => $label,
                    'required' => (bool)$required,
                    'validate' => $validate,
                ];
            }
        } elseif (in_array($type, ['delay_days','number','integer','time','workflow_steps'], true)) {
            // Numeric-ish types like delay_days -> use text with numeric min/max hints
            if ($type === 'workflow_steps' && $hasValues) {
                $item = [
                    'id' => $id,
                    'type' => 'single_choice',
                    'label' => $label,
                    'options' => $this->mapValuesToOptions($values),
                    'required' => (bool)$required,
                ];
            } elseif ($type === 'delay_days') {
                // Special UI hint for delay_days: render as input with trailing "days" addon in Assistant
                $item = [
                    'id' => $id,
                    'type' => 'text',
                    'label' => $label,
                    'required' => (bool)$required,
                    'validate' => $validate,
                    'ui' => [
                        'addon_suffix' => \mh_lng('UI_DAYS', 'days'),
                    ],
                ];
            } else {
                $item = [
                    'id' => $id,
                    'type' => 'text',
                    'label' => $label,
                    'required' => (bool)$required,
                    'validate' => $validate,
                ];
            }
        } else {
            // Default fallback: simple text input
            $item = [
                'id' => $id,
                'type' => 'text',
                'label' => $label,
                'required' => (bool)$required,
                'validate' => $validate,
            ];
        }

        if ($visibleIf) { $item['visible_if'] = $visibleIf; }
        return $item;
    }

    private function mapValuesToOptions(array $values): array
    {
        $opts = [];
        foreach ($values as $v) {
            $id = is_scalar($v) ? (string)$v : (is_array($v) && isset($v['id']) ? (string)$v['id'] : null);
            $label = is_array($v) ? ($v['label'] ?? $v['name'] ?? $id) : $id;
            if ($id === null) continue;
            $opts[] = ['id' => $id, 'label' => (string)$label];
        }
        return $opts;
    }

    private function humanize(string $id): string
    {
        // Remove common prefixes
        $s = (string)$id;
        $s = preg_replace('/^MAILBEEZ_/', '', $s) ?? $s;
        $s = preg_replace('/^[A-Z]+_/', '', $s) ?? $s; // drop module prefix
        $s = str_replace(['__','_'], ['_',' '], $s);
        $s = strtolower($s);
        $s = ucwords($s);
        return $s;
    }

    private function slugify(string $text): string
    {
        $text = strtolower(trim($text));
        $text = preg_replace('/[^a-z0-9]+/', '-', $text) ?? $text;
        return trim($text, '-');
    }

    // Build an index of items keyed by id for quick meta lookup
    private function itemsIndex(): array
    {
        static $idx = null;
        if ($idx !== null) return $idx;
        $idx = [];
        foreach ($this->cfg['groups'] as $g) {
            foreach ($g['steps'] as $s) {
                foreach (($s['items'] ?? []) as $it) {
                    if (!empty($it['id'])) { $idx[$it['id']] = $it; }
                }
            }
        }
        return $idx;
    }

    public function isItemVisible(array $item, array $answers): bool
    {
        return FieldVisibility::isVisible($item, $answers, $this->itemsIndex());
    }

    public function itemsForStep(array $step, array $answers): array
    {
        $items = $step['items'] ?? [];
        if (!$items) return [];
        $out = [];
        foreach ($items as $it) {
            if ($this->isItemVisible($it, $answers)) { $out[] = $it; }
        }
        return $out;
    }
}
