<?php

namespace garmayev\dynamicform;

use Yii;
use yii\base\Widget;
use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Json;

class DynamicForm extends Widget
{
    const RELATION_MANY_TO_MANY = 'many-to-many';
    const RELATION_ONE_TO_MANY = 'one-to-many';
    const RELATION_HAS_MANY = 'has-many';
    const RELATION_HAS_ONE = 'has-one';

    public $form;
    public $model;
    public $relation;
    public $relationType = self::RELATION_MANY_TO_MANY;
    public $relationModelClass;
    public $options = [];
    public $addButtonOptions = [];
    public $removeButtonOptions = [];
    public $itemsContainerClass = 'dynamic-items-container';
    public $itemClass = 'dynamic-form-item';
    public $showAddButton = true;
    public $showRemoveButton = true;
    public $allowRemoveLast = true;
    public $itemHeaderText = 'Item';
    public $showItemHeader = true;
    public $formName = null;

    private $_relationModels = [];
    private $_currentIndex = 0;
    private $_originalIds = [];
    private $_modelFormName = null;
    private $_templateHtml = null;
    private $_fieldClosures = [];
    private $_isTemplateCreated = false;
    private $_rawContent = '';

    public function init()
    {
        parent::init();

        $this->validateConfig();
        $this->initModelFormName();
        $this->initDefaultOptions();
        $this->loadRelationModels();

        ob_start();
        ob_implicit_flush(false);
    }

    protected function validateConfig()
    {
        if ($this->form === null) {
            throw new \InvalidArgumentException('Form property must be set');
        }

        if ($this->model === null) {
            throw new \InvalidArgumentException('Model property must be set');
        }

        if ($this->relation === null) {
            throw new \InvalidArgumentException('Relation property must be set');
        }
    }

    protected function initModelFormName()
    {
        if ($this->formName !== null) {
            $this->_modelFormName = $this->formName;
        } else {
            $className = (new \ReflectionClass($this->model))->getShortName();
            $this->_modelFormName = $className;
        }
    }

    protected function initDefaultOptions()
    {
        $this->options = ArrayHelper::merge([
            'id' => 'dynamic-form-' . $this->relation,
            'class' => 'dynamic-form-widget',
            'data-relation' => $this->relation,
            'data-model-form-name' => $this->_modelFormName,
        ], $this->options);

        $this->addButtonOptions = ArrayHelper::merge([
            'class' => 'btn btn-success btn-add-dynamic-item',
            'label' => '<i class="fas fa-plus"></i> Add',
            'type' => 'button',
        ], $this->addButtonOptions);

        $this->removeButtonOptions = ArrayHelper::merge([
            'class' => 'btn btn-danger btn-remove-dynamic-item btn-sm',
            'label' => '<i class="fas fa-trash"></i>',
            'title' => 'Remove',
            'type' => 'button',
        ], $this->removeButtonOptions);
    }

    protected function loadRelationModels()
    {
        if ($this->relationModelClass === null) {
            try {
                $relation = $this->model->getRelation($this->relation);
                $this->relationModelClass = $relation->modelClass;
            } catch (\Exception $e) {
                $this->relationModelClass = 'yii\base\DynamicModel';
            }
        }

        try {
            $models = $this->model->{$this->relation};
            $this->_relationModels = !empty($models) ? $models : [];

            if (!empty($this->_relationModels)) {
                $this->_originalIds = ArrayHelper::getColumn($this->_relationModels, 'id');
            }
        } catch (\Exception $e) {
            $this->_relationModels = [];
        }

        if (empty($this->_relationModels) && $this->relationType !== self::RELATION_HAS_ONE) {
            $modelClass = $this->relationModelClass;
            $this->_relationModels = [new $modelClass()];
        }

        if ($this->relationType === self::RELATION_HAS_ONE && empty($this->_relationModels)) {
            $modelClass = $this->relationModelClass;
            $this->_relationModels = [new $modelClass()];
        }
    }

    protected function createTemplate()
    {
        if ($this->_isTemplateCreated) {
            return;
        }

        if (empty($this->_rawContent) && empty($this->_fieldClosures)) {
            $this->_templateHtml = '';
            $this->_isTemplateCreated = true;
            return;
        }

        $oldIndex = $this->_currentIndex;
        $oldModels = $this->_relationModels;

        $this->_currentIndex = '__INDEX__';

        $emptyModel = new $this->relationModelClass();
        $this->_relationModels = [$emptyModel];

        $html = $this->_rawContent;

        // Заменяем плейсхолдеры на HTML для пустой модели
        foreach ($this->_fieldClosures as $placeholder => $closure) {
            $fieldHtml = $closure();
            // Пропускаем hidden поля в шаблоне
            if (strpos($fieldHtml, 'type="hidden"') !== false) {
                $fieldHtml = '';
            }
            $html = str_replace($placeholder, $fieldHtml, $html);
        }

        $html = $this->cleanTemplateValues($html);
        $html = preg_replace('/\s+/', ' ', $html);

        $this->_templateHtml = $html;
        $this->_isTemplateCreated = true;

        $this->_relationModels = $oldModels;
        $this->_currentIndex = $oldIndex;
    }

    protected function cleanTemplateValues($html)
    {
        if (empty($html)) {
            return $html;
        }

        // Очищаем value атрибуты у input (кроме hidden)
        $html = preg_replace_callback('/(<(input|textarea)([^>]*)>)/i', function($matches) {
            $tag = $matches[1];
            $type = $matches[2];
            $attributes = $matches[3];

            if ($type === 'input' && preg_match('/type=["\']hidden["\']/i', $attributes)) {
                return $tag;
            }

            $cleaned = preg_replace('/\s+value=["\'][^"\']*["\']/i', '', $tag);

            if ($type === 'input') {
                $cleaned = preg_replace('/\/?>$/', ' value="" />', $cleaned);
            }

            return $cleaned;
        }, $html);

        $html = preg_replace('/selected="selected"/', '', $html);
        $html = preg_replace('/\s+selected\b/', '', $html);
        $html = preg_replace('/checked="checked"/', '', $html);
        $html = preg_replace('/\s+checked\b/', '', $html);

        $html = preg_replace_callback('/(<select[^>]*>)(.*?)(<\/select>)/is', function($matches) {
            $selectOpen = $matches[1];
            $options = $matches[2];
            $selectClose = $matches[3];

            $options = preg_replace('/selected="selected"/', '', $options);
            $options = preg_replace('/\s+selected\b/', '', $options);

            return $selectOpen . $options . $selectClose;
        }, $html);

        return $html;
    }

    protected function addFieldClosure($placeholder, $closure)
    {
        $this->_fieldClosures[$placeholder] = $closure;
    }

    protected function getFieldName($attribute)
    {
        if ($this->relationType === self::RELATION_HAS_ONE) {
            return $this->_modelFormName . '[' . $this->relation . '][' . $attribute . ']';
        }
        return $this->_modelFormName . '[' . $this->relation . '][' . $this->_currentIndex . '][' . $attribute . ']';
    }

    protected function generateId($attribute)
    {
        $modelName = strtolower($this->_modelFormName);
        if ($this->relationType === self::RELATION_HAS_ONE) {
            return $modelName . '-' . $this->relation . '-' . $attribute;
        }
        return $modelName . '-' . $this->relation . '-' . $this->_currentIndex . '-' . $attribute;
    }

    protected function generatePlaceholder($attribute)
    {
        return '{{__FIELD_' . md5($attribute . microtime() . rand()) . '__}}';
    }

    public function textField($attribute, $label = null, $options = [])
    {
        $placeholder = $this->generatePlaceholder($attribute);

        $closure = function() use ($attribute, $label, $options) {
            $fieldName = $this->getFieldName($attribute);
            $currentModel = $this->getCurrentModel();

            $labelHtml = '';
            if ($label !== false && (!isset($options['type']) || $options['type'] !== 'hidden')) {
                $labelText = $label !== null ? $label : $currentModel->getAttributeLabel($attribute);
                $labelHtml = Html::label($labelText, $this->generateId($attribute), ['class' => 'form-label']);
            }

            $options['name'] = $fieldName;
            $options['id'] = $this->generateId($attribute);

            if (!isset($options['type']) || $options['type'] !== 'hidden') {
                $options['class'] = 'form-control';
            }

            $value = $options['value'] ?? $currentModel->$attribute;

            if (is_array($value)) {
                $value = '';
            }

            $options['value'] = $value;

            if ($currentModel->hasErrors($attribute) && (!isset($options['type']) || $options['type'] !== 'hidden')) {
                if (isset($options['class'])) {
                    $options['class'] .= ' is-invalid';
                }
            }

            $inputHtml = Html::textInput($fieldName, $value, $options);

            $errorHtml = '';
            if ($currentModel->hasErrors($attribute) && (!isset($options['type']) || $options['type'] !== 'hidden')) {
                $errors = $currentModel->getErrors($attribute);
                $errorHtml = Html::tag('div', implode('<br>', $errors), ['class' => 'invalid-feedback d-block']);
            }

            if (isset($options['type']) && $options['type'] === 'hidden') {
                return $inputHtml;
            }

            return Html::tag('div', $labelHtml . $inputHtml . $errorHtml, ['class' => 'form-group']);
        };

        $this->addFieldClosure($placeholder, $closure);

        return $placeholder;
    }

    public function dropDownListField($attribute, $items, $label = null, $options = [])
    {
        $placeholder = $this->generatePlaceholder($attribute);

        $closure = function() use ($attribute, $items, $label, $options) {
            $fieldName = $this->getFieldName($attribute);
            $currentModel = $this->getCurrentModel();

            $labelHtml = '';
            if ($label !== false) {
                $labelText = $label !== null ? $label : $currentModel->getAttributeLabel($attribute);
                $labelHtml = Html::label($labelText, $this->generateId($attribute), ['class' => 'form-label']);
            }

            $options['name'] = $fieldName;
            $options['id'] = $this->generateId($attribute);
            $options['class'] = 'form-select';

            if ($currentModel->hasErrors($attribute)) {
                $options['class'] .= ' is-invalid';
            }

            $value = $currentModel->$attribute;

            if (is_array($value)) {
                $value = null;
            }

            $inputHtml = Html::dropDownList($fieldName, $value, $items, $options);

            $errorHtml = '';
            if ($currentModel->hasErrors($attribute)) {
                $errors = $currentModel->getErrors($attribute);
                $errorHtml = Html::tag('div', implode('<br>', $errors), ['class' => 'invalid-feedback d-block']);
            }

            return Html::tag('div', $labelHtml . $inputHtml . $errorHtml, ['class' => 'form-group']);
        };

        $this->addFieldClosure($placeholder, $closure);

        return $placeholder;
    }

    public function textareaField($attribute, $label = null, $options = [])
    {
        $placeholder = $this->generatePlaceholder($attribute);

        $closure = function() use ($attribute, $label, $options) {
            $fieldName = $this->getFieldName($attribute);
            $currentModel = $this->getCurrentModel();

            $labelHtml = '';
            if ($label !== false) {
                $labelText = $label !== null ? $label : $currentModel->getAttributeLabel($attribute);
                $labelHtml = Html::label($labelText, $this->generateId($attribute), ['class' => 'form-label']);
            }

            $options['name'] = $fieldName;
            $options['id'] = $this->generateId($attribute);
            $options['class'] = 'form-control';
            $options['rows'] = 3;

            if ($currentModel->hasErrors($attribute)) {
                $options['class'] .= ' is-invalid';
            }

            $value = $currentModel->$attribute;

            if (is_array($value)) {
                $value = '';
            }

            $inputHtml = Html::textarea($fieldName, $value, $options);

            $errorHtml = '';
            if ($currentModel->hasErrors($attribute)) {
                $errors = $currentModel->getErrors($attribute);
                $errorHtml = Html::tag('div', implode('<br>', $errors), ['class' => 'invalid-feedback d-block']);
            }

            return Html::tag('div', $labelHtml . $inputHtml . $errorHtml, ['class' => 'form-group mb-0']);
        };

        $this->addFieldClosure($placeholder, $closure);

        return $placeholder;
    }

    public function checkboxField($attribute, $label = null, $options = [])
    {
        $placeholder = $this->generatePlaceholder($attribute);

        $closure = function() use ($attribute, $label, $options) {
            $fieldName = $this->getFieldName($attribute);
            $currentModel = $this->getCurrentModel();

            $options['name'] = $fieldName;
            $options['id'] = $this->generateId($attribute);
            $options['value'] = 1;
            $options['class'] = 'form-check-input';

            $checked = (bool)$currentModel->$attribute;

            $checkbox = Html::checkbox($fieldName, $checked, $options);

            if ($label) {
                $checkbox .= Html::label($label, null, ['class' => 'form-check-label ms-2']);
            }

            $errorHtml = '';
            if ($currentModel->hasErrors($attribute)) {
                $errors = $currentModel->getErrors($attribute);
                $errorHtml = Html::tag('div', implode('<br>', $errors), ['class' => 'invalid-feedback d-block']);
            }

            return Html::tag('div', $checkbox . $errorHtml, ['class' => 'form-group mb-0 form-check']);
        };

        $this->addFieldClosure($placeholder, $closure);

        return $placeholder;
    }

    protected function getCurrentModel()
    {
        if ($this->relationType === self::RELATION_HAS_ONE) {
            return $this->_relationModels[0] ?? new $this->relationModelClass();
        }

        if (isset($this->_relationModels[$this->_currentIndex])) {
            return $this->_relationModels[$this->_currentIndex];
        }

        $modelClass = $this->relationModelClass;
        return new $modelClass();
    }

    public function run()
    {
        $this->_rawContent = ob_get_clean();

        $this->createTemplate();

        return $this->renderWidget();
    }

    /**
     * Рендерит контент для конкретной модели
     */
    protected function renderContentForModel()
    {
        $content = $this->_rawContent;

        // Заменяем плейсхолдеры на HTML для текущей модели
        foreach ($this->_fieldClosures as $placeholder => $closure) {
            $content = str_replace($placeholder, $closure(), $content);
        }

        return $content;
    }

    protected function renderWidget()
    {
        $html = Html::beginTag('div', $this->options);

        $html .= Html::beginTag('div', [
            'class' => $this->itemsContainerClass,
            'data-relation' => $this->relation,
            'data-model-form-name' => $this->_modelFormName,
        ]);

        if ($this->relationType === self::RELATION_HAS_ONE) {
            $html .= $this->renderHasOneItem();
        } else {
            $html .= $this->renderHasManyItems();
        }

        $html .= Html::endTag('div');

        if ($this->showAddButton && $this->relationType !== self::RELATION_HAS_ONE) {
            $html .= Html::beginTag('div', ['class' => 'dynamic-form-actions', 'style' => 'margin-top: 15px; text-align: center;']);
            $html .= Html::button($this->addButtonOptions['label'], [
                'class' => $this->addButtonOptions['class'],
                'type' => 'button',
                'data-relation' => $this->relation,
            ]);
            $html .= Html::endTag('div');
        }

        if ($this->relationType !== self::RELATION_HAS_ONE && !empty($this->_templateHtml)) {
            $html .= $this->renderTemplate();
        } elseif ($this->relationType !== self::RELATION_HAS_ONE && empty($this->_templateHtml)) {
            $html .= $this->renderEmptyTemplate();
        }

        $html .= Html::endTag('div');

        $this->registerAssets();

        return $html;
    }

    protected function renderHasOneItem()
    {
        $model = $this->_relationModels[0] ?? null;
        if (!$model) {
            $modelClass = $this->relationModelClass;
            $model = new $modelClass();
        }

        $this->_currentIndex = 0;

        $html = Html::beginTag('div', [
            'class' => $this->itemClass,
            'data-index' => 0,
        ]);

        $html .= Html::beginTag('div', ['class' => 'card mb-3']);

        if ($this->showItemHeader) {
            $html .= Html::beginTag('div', ['class' => 'card-header bg-light']);
            $html .= Html::tag('h4', $this->itemHeaderText, ['class' => 'card-title h5']);
            $html .= Html::endTag('div');
        }

        $html .= Html::beginTag('div', ['class' => 'card-body']);
        $html .= $this->renderContentForModel();
        $html .= Html::endTag('div');

        $html .= Html::endTag('div');
        $html .= Html::endTag('div');

        return $html;
    }

    protected function renderHasManyItems()
    {
        $html = '';

        foreach ($this->_relationModels as $index => $model) {
            $html .= $this->renderHasManyItem($model, $index);
        }

        return $html;
    }

    protected function renderHasManyItem($model, $index)
    {
        $this->_currentIndex = $index;

        $html = Html::beginTag('div', [
            'class' => $this->itemClass,
            'data-index' => $index,
            'data-id' => $model->isNewRecord ? null : $model->getPrimaryKey(),
        ]);

        $html .= Html::beginTag('div', ['class' => 'card mb-3']);

        if ($this->showItemHeader || $this->showRemoveButton) {
            $html .= Html::beginTag('div', ['class' => 'card-header bg-light']);

            if ($this->showItemHeader) {
                $html .= Html::tag('h4', $this->itemHeaderText . ' #' . ($index + 1), [
                    'class' => 'card-title h5',
                    'style' => 'display: inline-block; margin-right: 10px;',
                ]);
            }

            if ($this->showRemoveButton) {
                $html .= Html::button($this->removeButtonOptions['label'], [
                    'class' => $this->removeButtonOptions['class'],
                    'title' => $this->removeButtonOptions['title'],
                    'type' => 'button',
                    'style' => 'float: right;',
                ]);
                $html .= Html::tag('div', '', ['style' => 'clear: both;']);
            }

            $html .= Html::endTag('div');
        }

        $html .= Html::beginTag('div', ['class' => 'card-body']);

        // Рендерим контент для текущей модели
        $html .= $this->renderContentForModel();

        $html .= Html::endTag('div');

        $html .= Html::endTag('div');
        $html .= Html::endTag('div');

        return $html;
    }

    protected function renderTemplate()
    {
        $wrappedTemplate = Html::beginTag('div', ['class' => $this->itemClass]);
        $wrappedTemplate .= Html::beginTag('div', ['class' => 'card mb-3']);

        if ($this->showItemHeader || $this->showRemoveButton) {
            $wrappedTemplate .= Html::beginTag('div', ['class' => 'card-header bg-light']);

            if ($this->showItemHeader) {
                $wrappedTemplate .= Html::tag('h4', $this->itemHeaderText . ' #__INDEX_PLUS_ONE__', [
                    'class' => 'card-title h5',
                    'style' => 'display: inline-block; margin-right: 10px;',
                ]);
            }

            if ($this->showRemoveButton) {
                $wrappedTemplate .= Html::button($this->removeButtonOptions['label'], [
                    'class' => $this->removeButtonOptions['class'],
                    'title' => $this->removeButtonOptions['title'],
                    'type' => 'button',
                    'style' => 'float: right;',
                ]);
                $wrappedTemplate .= Html::tag('div', '', ['style' => 'clear: both;']);
            }

            $wrappedTemplate .= Html::endTag('div');
        }

        $wrappedTemplate .= Html::beginTag('div', ['class' => 'card-body']);
        $wrappedTemplate .= $this->_templateHtml;
        $wrappedTemplate .= Html::endTag('div');

        $wrappedTemplate .= Html::endTag('div');
        $wrappedTemplate .= Html::endTag('div');

        return Html::tag('div', $wrappedTemplate, [
            'class' => 'dynamic-form-template',
            'style' => 'display: none;',
        ]);
    }

    protected function renderEmptyTemplate()
    {
        $wrappedTemplate = Html::beginTag('div', ['class' => $this->itemClass]);
        $wrappedTemplate .= Html::tag('div', 'No fields defined', ['class' => 'alert alert-warning']);
        $wrappedTemplate .= Html::endTag('div');

        return Html::tag('div', $wrappedTemplate, [
            'class' => 'dynamic-form-template',
            'style' => 'display: none;',
        ]);
    }

    protected function registerAssets()
    {
        $view = $this->getView();

        if (class_exists('garmayev\dynamicform\DynamicFormAsset')) {
            DynamicFormAsset::register($view);
        }

        $options = [
            'itemsContainer' => '.' . $this->itemsContainerClass,
            'itemClass' => '.' . $this->itemClass,
            'addButtonSelector' => '.btn-add-dynamic-item',
            'removeButtonSelector' => '.btn-remove-dynamic-item',
            'templateSelector' => '.dynamic-form-template',
            'relation' => $this->relation,
            'relationType' => $this->relationType,
            'allowRemoveLast' => $this->allowRemoveLast,
            'modelFormName' => $this->_modelFormName,
        ];

        $jsonOptions = Json::encode($options);

        $js = <<<JS
(function() {
    function initDynamicForm() {
        var \$el = $('#{$this->options['id']}');
        if (\$el.length && !\$el.data('dynamic-form-initialized')) {
            console.log('Initializing dynamic form for relation: {$this->relation}');
            \$el.garmayevDynamicForm($jsonOptions);
        }
    }
    
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initDynamicForm);
    } else {
        initDynamicForm();
    }
})();
JS;

        $view->registerJs($js);
    }
}