<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * CORS headers for the linkquiver/v1 REST namespace.
 *
 * Only browser-origin requests are constrained by CORS at all —
 * server-to-server calls (the LinkQuiver SaaS, Trigger.dev, any Node
 * runtime) don't send an `Origin` header and are not affected. The
 * allowlist exists to constrain front-end origins that might try to
 * call linkquiver/v1 from a page DOM if the API key ever leaks there.
 */
class Linkquiver_CORS {

    public static function init() {
        // Run on init (early) to catch preflight OPTIONS before WordPress
        // REST API kicks in, then again on rest_api_init for the actual call.
        add_action( 'init', array( __CLASS__, 'add_cors_headers' ), 1 );
        add_action( 'rest_api_init', array( __CLASS__, 'add_cors_headers' ) );
    }

    /**
     * Allowlist of origins permitted to call linkquiver/v1 from a browser.
     */
    private static function cors_allowed_origins() {
        // Production origin only. localhost is kept because it's only reachable
        // from the customer's own machine (and still key-gated). The former
        // ngrok dev tunnel was removed: ngrok-free.dev subdomains are recycled
        // to other users once torn down, so shipping one to every customer is a
        // dangling-origin risk. Extendable via filter for bespoke deployments.
        return apply_filters( 'linkquiver_cors_allowed_origins', array(
            'https://linkquiver.com',
            'http://localhost:3000',
            'http://localhost:3001',
        ) );
    }

    /**
     * Echo CORS headers for linkquiver/v1, reflecting the request `Origin`
     * back only if it matches the allowlist. No wildcard.
     */
    public static function add_cors_headers() {
        if ( ! self::is_linkquiver_request() ) {
            return;
        }

        $origin = isset( $_SERVER['HTTP_ORIGIN'] )
            ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ORIGIN'] ) )
            : '';
        $origin = rtrim( $origin, '/' );

        $allowed    = self::cors_allowed_origins();
        $is_allowed = ( '' !== $origin && in_array( $origin, $allowed, true ) );

        if ( ! headers_sent() ) {
            header_remove( 'Access-Control-Allow-Origin' );
            header_remove( 'Access-Control-Allow-Methods' );
            header_remove( 'Access-Control-Allow-Headers' );
            header_remove( 'Vary' );
        }

        if ( $is_allowed ) {
            header( 'Access-Control-Allow-Origin: ' . $origin );
            header( 'Vary: Origin' );
            header( 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS' );
            header( 'Access-Control-Allow-Headers: Content-Type, X-Linkquiver-Key, Authorization, Idempotency-Key' );
            header( 'Access-Control-Max-Age: 86400' );
        }

        // Preflight: return 204 regardless of allowlist match. Browsers
        // enforce CORS via the headers (or their absence). Non-browser
        // tools that send OPTIONS shouldn't choke either way.
        if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'OPTIONS' === $_SERVER['REQUEST_METHOD'] ) {
            status_header( 204 );
            exit;
        }
    }

    /**
     * True when the current request targets the linkquiver/v1 namespace.
     *
     * Strict regex anchored on `/wp-json/linkquiver/v1` followed by `/`,
     * `?`, or end-of-string to prevent path-confusion bypasses where an
     * unrelated route happens to contain "linkquiver/v1" as a substring
     * (e.g. `/wp-json/linkquiver/v1-evil/`).
     */
    private static function is_linkquiver_request() {
        if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
            return false;
        }

        $request_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );

        if ( preg_match( '#/wp-json/linkquiver/v1(/|\?|$)#', $request_uri ) ) {
            return true;
        }

        if ( isset( $_GET['rest_route'] ) ) {
            $route = sanitize_text_field( wp_unslash( $_GET['rest_route'] ) );
            if ( preg_match( '#^/linkquiver/v1(/|$)#', $route ) ) {
                return true;
            }
        }

        return false;
    }
}
