<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Settings;

use JTL\Backend\Settings\SectionFactory;
use JTL\DB\DbInterface;
use JTL\L10n\GetText;
use JTL\Services\JTL\AlertServiceInterface;
use JTL\Smarty\JTLSmarty;
use RuntimeException;

/**
 * Generic write side of the settings bridge: replay the shop's OWN admin save, never duplicate
 * its logic. The target is `SectionFactory::getSection($section,$manager)->update(...)` — the
 * exact call `AbstractBackendController::saveAdminSectionSettings()` makes (XSS filter, type
 * coercion, validation, `teinstellungen` delete+insert, changelog, cache flush all inherited for
 * free; `SectionFactory` auto-picks any section-specific subclass too). A bare DB write is
 * deliberately never an option here.
 */
final class SettingsWriter
{
    public function __construct(
        private readonly DbInterface $db,
        private readonly JTLSmarty $smarty,
        private readonly GetText $getText,
        private readonly AlertServiceInterface $alertService,
    ) {
    }

    /**
     * @return array{key: string, old: mixed, new: mixed}
     */
    public function write(SettingRow $row, mixed $value): array
    {
        if (!$row->isWritable() || $row->conf === null) {
            throw new RuntimeException('setting "' . $row->key . '" is not writable');
        }

        $conf    = $row->conf;
        $coerced = $this->coerce($conf->type, $value);

        $manager = new McpSettingsManager($this->db, $this->smarty, $this->getText, $this->alertService);
        $section = (new SectionFactory())->getSection($conf->section, $manager);
        // Load first (same source Section::update() would lazily load) so we can read the OLD
        // value straight off the hydrated Item — no separate Shop::getSettingValue() call, so the
        // writer stays a pure function of the injected DbInterface (FakeDb-testable).
        $section->load();
        $old = null;
        foreach ($section->getItems() as $item) {
            if ($item->getValueName() === $conf->name) {
                $old = $item->getCurrentValue();
                break;
            }
        }

        $updated = $section->update([$conf->name => $coerced], true, [\CACHING_GROUP_OPTION]);

        if ($section->getUpdateErrors() > 0 || $updated === []) {
            throw new RuntimeException('setting "' . $row->key . '" was rejected by the shop (validation failed)');
        }

        return ['key' => $row->key, 'old' => $old, 'new' => $coerced];
    }

    private function coerce(string $type, mixed $value): string|int|float|bool
    {
        return match ($type) {
            'int'   => (int)$value,
            'float' => (float)$value,
            'bool'  => (bool)$value,
            default => (string)$value,
        };
    }
}
