<?php
namespace EncypherProvenance;

if (! defined('ABSPATH')) {
    exit;
}

/**
 * Canonical operator status for post provenance and publication assurance.
 *
 * Provenance answers whether the candidate bytes are signed. Publication
 * assurance answers whether the expected signed generation is live. They are
 * kept separate so a stale Signed or Verified fact cannot hide a failed
 * delivery.
 */
final class ContentStatus
{
    /**
     * Resolve stored provenance facts to the six authoring states.
     *
     * The optional assurance input is the stored publication-assurance state
     * or its state string. It exists to preserve the original four-argument
     * API while making the operator-facing aggregate fail closed.
     *
     * @param mixed $last_sign_error      Raw or normalized signing error.
     * @param mixed $publication_assurance Raw assurance state or state array.
     */
    public static function resolve(
        string $provenance_status,
        bool $generation_pending,
        bool $signing_in_progress,
        $last_sign_error,
        $publication_assurance = null
    ): string {
        if ($generation_pending || $signing_in_progress) {
            return 'signing';
        }

        if (
            self::assurance_failed($publication_assurance)
            && in_array($provenance_status, ['c2pa_verified', 'verified', 'c2pa_protected', 'signed'], true)
        ) {
            return 'needs_attention';
        }

        if (
            ! empty($last_sign_error)
            || in_array(
                $provenance_status,
                ['verification_failed', 'embedding_failed', 'media_signing_failed', 'tampered', 'signing_stalled'],
                true
            )
        ) {
            return 'needs_attention';
        }

        if (in_array($provenance_status, ['c2pa_verified', 'verified'], true)) {
            return 'verified';
        }

        if (in_array($provenance_status, ['c2pa_protected', 'signed'], true)) {
            return 'signed';
        }

        if ('modified' === $provenance_status) {
            return 'edited';
        }

        return 'not_signed';
    }

    /**
     * The one mapping from a stored assurance state to the canonical enum:
     * unknown | held | live_unverified | live_verified | failed | cancelled |
     * superseded.
     *
     * The publication side is normalized without collapsing it into
     * provenance. An unrecognized value stays unknown rather than being read
     * as successful delivery, and an emergency unsigned publication is live
     * but never verified.
     *
     * @param mixed $publication_assurance Raw assurance state or state array.
     */
    public static function assurance_state($publication_assurance): string
    {
        $state = is_array($publication_assurance)
            ? (string) ($publication_assurance['state'] ?? '')
            : (is_string($publication_assurance) ? $publication_assurance : '');

        if (in_array($state, ['signing_failed', 'public_delivery_failed', 'live_verification_failed', 'delivery_failed', 'publication_failed'], true)) {
            return 'failed';
        }
        if (in_array($state, ['preparing', 'preparing_update', 'signed_scheduled', 'held', 'signing'], true)) {
            return 'held';
        }
        if (in_array($state, ['live_verified', 'verified_live'], true)) {
            return 'live_verified';
        }
        if (
            in_array(
                $state,
                ['published_pending_live_verification', 'released', 'published', 'live_check_pending', 'emergency_unsigned'],
                true
            )
        ) {
            return 'live_unverified';
        }
        if (in_array($state, ['cancelled', 'cancelled_restored_public', 'cancelled_restored_draft'], true)) {
            return 'cancelled';
        }
        if ('superseded' === $state) {
            return 'superseded';
        }

        return 'unknown';
    }

    /**
     * Whether publication assurance carries a terminal delivery failure.
     *
     * @param mixed $publication_assurance Raw assurance state or state array.
     */
    public static function assurance_failed($publication_assurance): bool
    {
        return 'failed' === self::assurance_state($publication_assurance);
    }

    /**
     * Return both truth dimensions plus the conservative display state.
     *
     * @return array{display_state:string,provenance_state:string,assurance_state:string,assurance:array<string,mixed>}
     */
    public static function details_for_post(int $post_id): array
    {
        $raw_signing_flag = get_post_meta($post_id, '_encypher_signing_in_progress', true);
        $stalled = ! empty($raw_signing_flag)
            && SigningStatus::flag_is_stale($raw_signing_flag, time());
        $provenance_status = (string) get_post_meta($post_id, '_encypher_provenance_status', true);
        $assurance = get_post_meta($post_id, '_encypher_publication_assurance', true);
        $assurance = is_array($assurance) ? $assurance : [];
        $provenance_state = self::resolve(
            $provenance_status,
            '' !== (string) get_post_meta($post_id, '_encypher_provenance_generation_pending', true),
            ! empty($raw_signing_flag) && ! $stalled,
            $stalled ?: get_post_meta($post_id, '_encypher_last_sign_error', true)
        );

        return [
            'display_state' => (
                self::assurance_failed($assurance)
                && in_array($provenance_state, ['signed', 'verified'], true)
            ) ? 'needs_attention' : $provenance_state,
            'provenance_state' => $provenance_state,
            'assurance_state' => self::assurance_state($assurance),
            'assurance' => $assurance,
        ];
    }

    /**
     * Read the canonical facts for one post without mutating them.
     */
    public static function for_post(int $post_id): string
    {
        $details = self::details_for_post($post_id);
        return $details['display_state'];
    }
}
