<?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 );
    }
}
