<?php

declare(strict_types=1);

namespace Acms\Services\Field;

use Acms\Services\Validator\MimeTypeValidator;
use Field;
use Field_Validation;

/**
 * image / file 型カスタムフィールド 1 つ分のアップロード処理を担うハンドラの基底。
 *
 * 両型で共通する道具（候補の正規化・MIME 検証・削除予約のマーキング・生成通知）をここに集約し、
 * 型ごとの差分をサブクラスの handle() に閉じる。
 *
 * Why not $post を コンストラクタで受け取らない: FieldExtractor は
 * `$this->Post =& $app->getPostParameter()` と参照で保持しており、takeover 処理で
 * Application 側が POST オブジェクトを差し替える経路がある。ハンドラは
 * コンテナの singleton として一度だけ生成されるため、生成時の POST を抱えると
 * 差し替え後に古いオブジェクトを掴み続ける。呼び出しごとに渡す。
 */
abstract class UploadFieldHandler
{
    /**
     * @var UploadedFilesNormalizer
     */
    protected UploadedFilesNormalizer $normalizer;

    /**
     * @var MimeTypeValidator
     */
    protected MimeTypeValidator $mimeTypeValidator;

    /**
     * アップロードされたメディアファイルの生成を通知するコールバック（実パスを1引数で受ける）
     *
     * @var (callable(string): void)|null
     */
    private $onMediaCreate;

    /**
     * Why not クラス内で ACMS_Hook を直接呼ばない: フック発火はグローバル状態（HookFactory /
     * HOOK_ENABLE）へのアクセスを伴うため、呼び出し側の private メソッドに閉じる規約
     * （development-guidelines.md「サービス層」）に従う。既定 null は、コンテナを経由せず
     * 直接 new する単体テストのためのフォールバック（フックを発火しない）。
     *
     * @param (callable(string): void)|null $onMediaCreate
     */
    public function __construct(
        UploadedFilesNormalizer $normalizer,
        MimeTypeValidator $mimeTypeValidator,
        ?callable $onMediaCreate = null
    ) {
        $this->normalizer = $normalizer;
        $this->mimeTypeValidator = $mimeTypeValidator;
        $this->onMediaCreate = $onMediaCreate;
    }

    /**
     * 1 フィールド分のアップロード処理を行い、結果を $field / $deleteField に書き込む。
     *
     * @param string $fd フィールドキー（例: 'photo'）
     * @param Field_Validation $field 抽出中のカスタムフィールド
     * @param Field $post 抽出中の POST（参照の所有は FieldExtractor 側）
     * @param Field $deleteField 削除対象として積む Field
     * @param ProcessingFiles $processing このリクエストでアップロード処理中のファイル（image / file で共有）
     * @param array<int, string> $allowFileExtensions アップロードを許可する拡張子
     */
    abstract public function handle(
        string $fd,
        Field_Validation $field,
        Field $post,
        Field $deleteField,
        ProcessingFiles $processing,
        array $allowFileExtensions
    ): void;

    /**
     * 削除チェック・差し替え時に旧ファイルを save() 後に物理削除するよう Field にマーキングする。
     *
     * ここでは即時削除せず、save() の DB 書き込み完了後に FieldFileGarbageCollector が
     * 参照チェックを通してから削除する。
     *
     * @param Field $field extract 中の Field
     * @param string $fieldKey image/file フィールドの key（例: 'photo'）
     * @param string $relPath archives ディレクトリからの相対パス
     * @param 'image'|'file' $type
     */
    protected function markFieldFileForRemoval(Field $field, string $fieldKey, string $relPath, string $type): void
    {
        if ($relPath === '') {
            return;
        }
        $removeList = $field->getMeta($fieldKey, 'removeOld');
        if (!is_array($removeList)) {
            $removeList = [];
        }
        $removeList[] = ['path' => $relPath, 'type' => $type];
        $field->setMeta($fieldKey, 'removeOld', $removeList);
    }

    /**
     * メディアファイルが新規に作られたことを通知する。
     */
    protected function notifyMediaCreated(string $realPath): void
    {
        if ($this->onMediaCreate !== null) {
            ($this->onMediaCreate)($realPath);
        }
    }
}
