<?php

if ( ! defined( 'ABSPATH' ) ) {

    exit;

}



/**

 * 設定リポジトリ。

 *

 * 1 つの wp_options エントリ (例: 'aiap_settings' / 'aiap_image_settings') に対する

 * 読み書き・既定値マージ・API キーの暗号化/復号を一手に引き受ける汎用リポジトリ。

 *

 * 旧コードは AIAP_AI_Client と AIAP_Image_Client が「defaults 配列・wp_parse_args・

 * 生オプション I/O・AUTH_KEY 欠落時のキー消失対策 (audit r9)」をそれぞれ別個に

 * 手書きしており、同一の不具合を 2 箇所に重複して修正する羽目になっていた。

 * その仕組みをこのクラス 1 つに集約し、各クライアントは「どのオプションキーか」

 * 「既定値スキーマは何か」「どのフィールドが API キーか」を渡すだけにする。

 *

 * 使い方:

 *   $store = new AIAP_Settings( 'aiap_settings', array( 'MyClass', 'defaults' ), $key_fields );

 *   $all   = $store->get_decrypted();        // 復号済みの全設定

 *   $store->update_encrypted( $new_values ); // 暗号化して保存

 */

class AIAP_Settings {



    /** @var string 対象の wp_options 名 */

    private $option_key;



    /** @var callable 既定値スキーマを返す callable (遅延評価: Model_Registry 依存のため) */

    private $defaults_provider;



    /** @var string[] 暗号化対象の API キーフィールド名 */

    private $key_fields;



    /**

     * @param string   $option_key        wp_options 名

     * @param callable $defaults_provider 既定値配列を返す callable

     * @param string[] $key_fields        暗号化対象フィールド (省略時は暗号レイヤなし)

     */

    public function __construct( $option_key, callable $defaults_provider, array $key_fields = array() ) {

        $this->option_key        = (string) $option_key;

        $this->defaults_provider = $defaults_provider;

        $this->key_fields        = $key_fields;

    }



    /**

     * 既定値スキーマを返す。

     *

     * @return array<string,mixed>

     */

    public function defaults() {

        $defaults = call_user_func( $this->defaults_provider );

        return is_array( $defaults ) ? $defaults : array();

    }



    /**

     * DB に保存されている生の値を返す (既定値マージなし)。

     * 暗号化済み API キーを温存したい保存処理などで使う。

     *

     * @return array<string,mixed>

     */

    public function raw() {

        $saved = get_option( $this->option_key, array() );

        return is_array( $saved ) ? $saved : array();

    }



    /**

     * 既定値とマージ済みの全設定を返す (DB 保存形のまま=API キーは未復号)。

     *

     * @return array<string,mixed>

     */

    public function all() {

        return wp_parse_args( $this->raw(), $this->defaults() );

    }



    /**

     * 設定値を 1 件取得する。

     *

     * 注意: API キー系フィールドは復号されない。復号済みの値が必要なら

     * get_decrypted() を使うこと。

     *

     * @param string $key

     * @param mixed  $fallback 未定義キー時の戻り値

     * @return mixed

     */

    public function get( $key, $fallback = null ) {

        $all = $this->all();

        return array_key_exists( $key, $all ) ? $all[ $key ] : $fallback;

    }



    /**

     * 設定値を真偽値として取得する (0/1 や ''/'1' のフラグ用)。

     *

     * @param string $key

     * @return bool

     */

    public function get_bool( $key ) {

        return ! empty( $this->get( $key ) );

    }



    /**

     * 設定値を整数として取得する。

     *

     * @param string $key

     * @param int    $fallback

     * @return int

     */

    public function get_int( $key, $fallback = 0 ) {

        return (int) $this->get( $key, $fallback );

    }



    /**

     * 設定値を文字列として取得する。

     *

     * @param string $key

     * @param string $fallback

     * @return string

     */

    public function get_string( $key, $fallback = '' ) {

        $value = $this->get( $key, $fallback );

        return is_scalar( $value ) ? (string) $value : $fallback;

    }



    /**

     * 設定を保存する (渡された配列をそのまま書き込む。暗号化はしない)。

     *

     * autoload=false: 暗号化済み API キーや system_prompt 等を含む大きめの

     * オプションであり、フロントの全リクエストで自動ロードする必要はない。

     *

     * @param array<string,mixed> $settings

     */

    public function save( array $settings ) {

        update_option( $this->option_key, $settings, false );

    }



    /**

     * API キーを復号した全設定を返す。

     *

     * @return array<string,mixed>

     */

    public function get_decrypted() {

        return AIAP_Crypto::decrypt_keys( $this->all(), $this->key_fields );

    }



    /**

     * API キーを暗号化して保存する。

     *

     * Bug fix (audit r9): AUTH_KEY 欠落 (または openssl 拡張なし) 環境では

     * get_decrypted -> decrypt_keys が API キー fields を空文字に置換して返す。

     * その状態で他設定だけ変更して保存すると、暗号化済みの既存キーが空で

     * 上書きされ、ユーザーの API キーが永久に失われる。Crypto が利用できない

     * 場合は、API キー fields だけ DB の既存値を読み戻して温存する。

     *

     * この対策は旧コードで AI 用・画像用の 2 箇所に重複していたが、本メソッドに

     * 集約したことで一元化された。

     *

     * @param array<string,mixed> $settings

     */

    public function update_encrypted( array $settings ) {

        if ( ! empty( $this->key_fields ) && ! AIAP_Crypto::is_available() ) {

            $existing = $this->raw();

            foreach ( $this->key_fields as $field ) {

                if ( ! empty( $existing[ $field ] ) ) {

                    // DB の既存値 (暗号文または旧平文) をそのまま温存

                    $settings[ $field ] = $existing[ $field ];

                }

            }

        }

        $this->save( AIAP_Crypto::encrypt_keys( $settings, $this->key_fields ) );

    }



    /* ===== 検証済みプロバイダ記録 =====

     * 単一のグローバルオプション。プロファイル (option_key) に依存しないため静的。 */



    /** API キー検証済みプロバイダ記録のオプション名。 */

    const OPTION_VERIFIED = 'aiap_verified_providers';



    /**

     * API キー検証済みプロバイダの記録を返す ( provider => 検証時刻 )。

     *

     * @return array<string,int>

     */

    public static function verified_providers() {

        $verified = get_option( self::OPTION_VERIFIED, array() );

        return is_array( $verified ) ? $verified : array();

    }



    /**

     * API キー検証済みプロバイダの記録を保存する。

     *

     * @param array<string,int> $providers

     */

    public static function set_verified_providers( array $providers ) {

        update_option( self::OPTION_VERIFIED, $providers, false );

    }

}

