<?php
/**
 * Copyright (C) 2017-present, Meta, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * @package FacebookPixelPlugin
 */

namespace FacebookPixelPlugin\Core;

use FacebookPixelPlugin\FacebookAds\Object\ServerSide\Event;

defined( 'ABSPATH' ) || die( 'Direct access not allowed' );

/**
 * Static per-request state for held signal delivery.
 */
class FacebookSignalState {
    /**
     * Cookie name for signal state.
     *
     * @var string
     */
    const COOKIE_NAME = 'wc_facebook_signals_state';

    /**
     * Whether signals are currently held for this request.
     *
     * @var bool
     */
    private static $held = false;

    /**
     * CAPI events queued while signals are held, keyed by event_id.
     *
     * @var array<string, Event>
     */
    private static $queued_events = array();

    /**
     * Attribution data captured while signals are held (e.g. fbp, fbc).
     *
     * @var array<string, string>
     */
    private static $attribution_data = array();

    /**
     * Hold signals for the current request.
     *
     * @return void
     */
    public static function hold() {
        self::$held = true;
        self::set_state_cookie( 'held' );
    }

    /**
     * Release signals for the current request.
     *
     * @return void
     */
    public static function release() {
        self::$held = false;
        self::set_state_cookie( 'active' );
    }

    /**
     * Whether signals are currently held.
     *
     * @return bool
     */
    public static function is_held() {
        return (bool) apply_filters(
            'facebook_signals_held',
            self::$held
        );
    }

    /**
     * Queue a CAPI event so it can be enriched on release.
     *
     * @param Event $event Event to queue.
     * @return void
     */
    public static function queue_event( Event $event ) {
        $event_id = $event->getEventId();
        if ( empty( $event_id ) ) {
            return;
        }
        self::$queued_events[ $event_id ] = $event;
    }

    /**
     * Get a queued CAPI event by event_id.
     *
     * @param string $event_id Event identifier.
     * @return Event|null
     */
    public static function get_queued_event( $event_id ) {
        return isset( self::$queued_events[ $event_id ] )
            ? self::$queued_events[ $event_id ]
            : null;
    }

    /**
     * Get the safe-to-forward normalized user_data for a queued event.
     *
     * Strips fields that the release request will derive from its own context
     * (client_ip_address, client_user_agent, fbp, fbc) so the release can build
     * those from cookies/headers it actually sees.
     *
     * @param string $event_id Event identifier.
     * @return array|null
     */
    public static function get_queued_user_data( $event_id ) {
        $event = self::get_queued_event( $event_id );
        if ( null === $event ) {
            return null;
        }

        $user_data = $event->getUserData();
        if ( null === $user_data ) {
            return null;
        }

        $normalized = $user_data->normalize();
        unset(
            $normalized['client_ip_address'],
            $normalized['client_user_agent'],
            $normalized['fbp'],
            $normalized['fbc']
        );

        return ! empty( $normalized ) ? $normalized : null;
    }

    /**
     * Store attribution data captured while signals are held.
     *
     * @param string $key   Data key (e.g. 'fbp', 'fbc').
     * @param string $value Data value.
     * @return void
     */
    public static function set_attribution_data( $key, $value ) {
        self::$attribution_data[ $key ] = $value;
    }

    /**
     * Retrieve stored attribution data.
     *
     * @param string $key Data key.
     * @return string|null
     */
    public static function get_attribution_data( $key ) {
        return isset( self::$attribution_data[ $key ] )
            ? self::$attribution_data[ $key ]
            : null;
    }

    /**
     * Reset the queued events and attribution stores. Intended for tests.
     *
     * @return void
     */
    public static function reset_queue() {
        self::$queued_events    = array();
        self::$attribution_data = array();
    }

    /**
     * Sets the signal-state cookie so client-side JS can read the current
     * state on cached pages where PHP cannot determine it at render time.
     *
     * @param string $state 'held' or 'active'.
     */
    private static function set_state_cookie( $state ) {
        if ( headers_sent() ) {
            return;
        }

        setcookie(
            self::COOKIE_NAME,
            $state,
            time() + YEAR_IN_SECONDS,
            defined( 'COOKIEPATH' ) ? COOKIEPATH : '/',
            defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : '',
            is_ssl(),
            false // httponly=false so JS can read it.
        );

        $_COOKIE[ self::COOKIE_NAME ] = $state;
    }
}
