import 'package:flutter/material.dart';

import '../../core/contracts/script_widget.dart';
import '../../shared/theme/app_theme.dart';
import '../../shared/widgets/setting_select.dart';

class ScriptWidgetParameterForm extends StatefulWidget {
  final List<ScriptWidgetParam> parameters;
  final Map<String, Object?> initialValues;
  final ValueChanged<Map<String, Object?>> onChanged;
  final bool hidePagingParameters;

  const ScriptWidgetParameterForm({
    super.key,
    required this.parameters,
    required this.initialValues,
    required this.onChanged,
    this.hidePagingParameters = false,
  });

  @override
  State<ScriptWidgetParameterForm> createState() =>
      _ScriptWidgetParameterFormState();
}

class _ScriptWidgetParameterFormState extends State<ScriptWidgetParameterForm> {
  final Map<String, TextEditingController> _textControllers =
      <String, TextEditingController>{};
  late Map<String, Object?> _values;

  @override
  void initState() {
    super.initState();
    _values = _buildInitialValues();
    _synchronizeTextControllers();
  }

  @override
  void didUpdateWidget(covariant ScriptWidgetParameterForm oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.parameters != widget.parameters ||
        oldWidget.initialValues != widget.initialValues) {
      _values = _buildInitialValues();
      _synchronizeTextControllers();
    }
  }

  @override
  void dispose() {
    for (final controller in _textControllers.values) {
      controller.dispose();
    }
    super.dispose();
  }

  Map<String, Object?> _buildInitialValues() {
    final defaultValues = buildScriptWidgetParameterDefaults(widget.parameters);
    return <String, Object?>{
      for (final parameter in widget.parameters)
        parameter.name:
            widget.initialValues[parameter.name] ??
            defaultValues[parameter.name],
    };
  }

  void _synchronizeTextControllers() {
    final activeNames = widget.parameters
        .where((parameter) => !_usesOptionSelector(parameter))
        .map((parameter) => parameter.name)
        .toSet();
    final staleNames = _textControllers.keys
        .where((name) => !activeNames.contains(name))
        .toList(growable: false);
    for (final name in staleNames) {
      _textControllers.remove(name)?.dispose();
    }
    for (final parameter in widget.parameters) {
      if (_usesOptionSelector(parameter)) continue;
      final value = _values[parameter.name]?.toString() ?? '';
      final controller = _textControllers.putIfAbsent(
        parameter.name,
        TextEditingController.new,
      );
      if (controller.text != value) {
        controller.value = TextEditingValue(
          text: value,
          selection: TextSelection.collapsed(offset: value.length),
        );
      }
    }
  }

  bool _usesOptionSelector(ScriptWidgetParam parameter) {
    return parameter.type == 'enumeration' ||
        parameter.enumOptions.isNotEmpty ||
        parameter.placeholders.isNotEmpty;
  }

  bool _isVisible(ScriptWidgetParam parameter) {
    if (parameter.type == 'constant') return false;
    if (widget.hidePagingParameters &&
        const <String>{'page', 'offset'}.contains(parameter.type)) {
      return false;
    }
    final condition = parameter.belongTo;
    if (condition == null || condition.paramName.isEmpty) return true;
    final parentValue = _values[condition.paramName]?.toString() ?? '';
    return condition.value.contains(parentValue);
  }

  void _updateValue(String name, Object? value) {
    setState(() {
      _values[name] = value;
    });
    widget.onChanged(Map<String, Object?>.unmodifiable(_values));
  }

  @override
  Widget build(BuildContext context) {
    final visibleParameters = widget.parameters
        .where(_isVisible)
        .toList(growable: false);
    if (visibleParameters.isEmpty) return const SizedBox.shrink();

    return Wrap(
      spacing: 12,
      runSpacing: 12,
      children: [
        for (final parameter in visibleParameters)
          SizedBox(
            width: _fieldWidth(parameter),
            child: _buildField(parameter),
          ),
      ],
    );
  }

  double _fieldWidth(ScriptWidgetParam parameter) {
    if (const <String>{'count', 'page', 'offset'}.contains(parameter.type)) {
      return 132;
    }
    return 240;
  }

  Widget _buildField(ScriptWidgetParam parameter) {
    final options = parameter.enumOptions.isNotEmpty
        ? parameter.enumOptions
        : parameter.placeholders;
    if (_usesOptionSelector(parameter)) {
      final selectedValue = _values[parameter.name]?.toString() ?? '';
      final optionValues = options.map((option) => option.value).toSet();
      final effectiveValue = optionValues.contains(selectedValue)
          ? selectedValue
          : null;
      final labelsByValue = <String, String>{
        for (final option in options)
          option.value: option.title.isEmpty ? option.value : option.title,
      };
      final theme = Theme.of(context);
      final tokens = context.appTokens;
      final helper = _helperFor(parameter);
      return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            _labelFor(parameter),
            style: theme.textTheme.labelMedium?.copyWith(
              fontWeight: FontWeight.w600,
            ),
          ),
          const SizedBox(height: 6),
          SettingSelect<String>(
            value: effectiveValue,
            options: options
                .map((option) => option.value)
                .toList(growable: false),
            labelOf: (value) => labelsByValue[value] ?? value,
            onChanged: (value) => _updateValue(parameter.name, value ?? ''),
          ),
          if (helper != null) ...[
            const SizedBox(height: 4),
            Text(
              helper,
              style: theme.textTheme.bodySmall?.copyWith(
                color: tokens.mutedForeground,
              ),
            ),
          ],
        ],
      );
    }

    final isNumeric = const <String>{
      'count',
      'page',
      'offset',
    }.contains(parameter.type);
    final theme = Theme.of(context);
    final tokens = context.appTokens;
    final inputBorder = OutlineInputBorder(
      borderRadius: BorderRadius.circular(8),
      borderSide: BorderSide(
        color: tokens.mutedForeground.withValues(alpha: 0.28),
      ),
    );
    return TextField(
      controller: _textControllers[parameter.name],
      keyboardType: isNumeric ? TextInputType.number : TextInputType.text,
      decoration: InputDecoration(
        labelText: _labelFor(parameter),
        helperText: _helperFor(parameter),
        filled: true,
        fillColor: theme.colorScheme.surface.withValues(alpha: 0.42),
        border: inputBorder,
        enabledBorder: inputBorder,
        focusedBorder: inputBorder.copyWith(
          borderSide: BorderSide(color: theme.colorScheme.primary),
        ),
        isDense: true,
      ),
      onChanged: (value) {
        final parsedValue = isNumeric ? int.tryParse(value) ?? value : value;
        _updateValue(parameter.name, parsedValue);
      },
    );
  }

  String _labelFor(ScriptWidgetParam parameter) {
    return parameter.title.isEmpty ? parameter.name : parameter.title;
  }

  String? _helperFor(ScriptWidgetParam parameter) {
    final description = parameter.description.trim();
    return description.isEmpty ? null : description;
  }
}
