<?php

namespace Acms\Services\ConfigSet;

use Symfony\Component\Yaml\Yaml;

/**
 * コンフィグセット単体のエクスポート処理。フォーム/モジュールのエクスポートと同様に専用クラスとして用意する。
 *
 * 永続化・変換は {@see RepositoryInterface} に閉じる。インポートは {@see Import} に分離している。
 * エクスポートは再インポート可能な原本（生レコード）を出力するため、Repository::findRawById /
 * findConfigRows を用いる。認可（config_set 管理権限）と CSRF はハンドラ側の責務とする。
 */
class Export
{
    /**
     * カテゴリーIDを値として保持する config キー。
     *
     * {@see Import::CATEGORY_ID_CONFIG_KEYS} と同じ一覧。category_id はブログごとの
     * auto-increment で他ブログと値の互換性が無いため、エクスポート時に安定した自然キーである
     * category_code へ変換しておく（インポート側で先方ブログの category_code から
     * category_id を引き直す。CMS-7831）。
     *
     * @var array<string>
     */
    private const CATEGORY_ID_CONFIG_KEYS = [
        'entry_edit_category_default',
        'entry_edit_category_filter',
        'entry_edit_sub_category_filter',
        'static_page_cid',
        'static_archive_cid',
    ];

    /**
     * @var RepositoryInterface
     */
    private $repository;

    /**
     * @param RepositoryInterface|null $repository
     */
    public function __construct(?RepositoryInterface $repository = null)
    {
        $this->repository = $repository ?? new Repository();
    }

    /**
     * コンフィグセット1件をエクスポート用の配列に変換する
     *
     * @param int $configSetId
     * @return array<string, mixed>
     */
    public function toArray($configSetId)
    {
        $configSet = $this->repository->findRawById($configSetId);
        if ($configSet === null) {
            return [];
        }

        $configRows = array_filter($this->repository->findConfigRows($configSetId), [$this, 'isNotRuleSpecific']);

        return [
            'config_set' => [$configSet],
            'config' => array_map([$this, 'convertCategoryIdToCode'], array_values($configRows)),
        ];
    }

    /**
     * カテゴリー参照キーの config_value を category_id から category_code へ変換する。
     * 変換できない（category_code が未解決）場合は元の値のまま返す
     * （インポート側で持ち込み先ブログとの突合に失敗し、従来通りクリアされる）。
     *
     * @param array<string, mixed> $record
     * @return array<string, mixed>
     */
    private function convertCategoryIdToCode(array $record): array
    {
        $key = (string) ($record['config_key'] ?? '');
        if (!in_array($key, self::CATEGORY_ID_CONFIG_KEYS, true)) {
            return $record;
        }

        $value = (string) ($record['config_value'] ?? '');
        if ($value === '' || !ctype_digit($value)) {
            return $record;
        }

        $code = $this->repository->findCategoryCodeById((int) $value);
        if ($code !== null) {
            $record['config_value'] = $code;
        }

        return $record;
    }

    /**
     * config_rule_id（ルール別上書き設定の紐付け先）が設定されている行はエクスポート対象外にする。
     * rule_id はブログごとの auto-increment で他ブログと値の互換性が無く、かつ rule_name は
     * 一意性の保証が無い（同名の別ルールを複数作成できる）ため、ルール名による自動突合は
     * 意図しないルールへの誤った紐付けを招く可能性がある。ルールの表示条件そのものが
     * インポート先で同じ意味を持つとも限らないため、ルール別上書きは常に対象外とする（CMS-7831）。
     *
     * @param array<string, mixed> $record
     */
    private function isNotRuleSpecific(array $record): bool
    {
        return (int) ($record['config_rule_id'] ?? 0) < 1;
    }

    /**
     * コンフィグセット1件を YAML 文字列としてエクスポートする
     *
     * @param int $configSetId
     * @return string
     */
    public function toYaml($configSetId)
    {
        return Yaml::dump($this->toArray($configSetId), 4, 2);
    }
}
