<?php
/**
 * Tax REST API Controller.
 *
 * Admin endpoints (require manage_options):
 *
 *   Tax Classes:
 *   GET    /titancart/v1/tax/classes              List all active classes
 *   POST   /titancart/v1/tax/classes              Create a class
 *   GET    /titancart/v1/tax/classes/{id}         Get a class
 *   PUT    /titancart/v1/tax/classes/{id}         Update a class
 *   DELETE /titancart/v1/tax/classes/{id}         Disable a class
 *
 *   Tax Zones:
 *   GET    /titancart/v1/tax/zones                List all active zones
 *   POST   /titancart/v1/tax/zones                Create a zone
 *   GET    /titancart/v1/tax/zones/{id}           Get a zone (with locations)
 *   PUT    /titancart/v1/tax/zones/{id}           Update a zone
 *   DELETE /titancart/v1/tax/zones/{id}           Delete a zone (cascades to rates)
 *
 *   POST   /titancart/v1/tax/zones/{id}/locations            Add a location
 *   DELETE /titancart/v1/tax/zones/{id}/locations/{loc_id}  Remove a location
 *
 *   Tax Rates:
 *   GET    /titancart/v1/tax/rates                List rates (filterable by class_id, zone_id)
 *   POST   /titancart/v1/tax/rates                Create a rate
 *   GET    /titancart/v1/tax/rates/{id}           Get a rate
 *   PUT    /titancart/v1/tax/rates/{id}           Update a rate
 *   DELETE /titancart/v1/tax/rates/{id}           Delete a rate
 *
 * Public endpoint (no auth):
 *   POST   /titancart/v1/checkout/tax             Calculate tax for a cart + address
 *
 * @package TitanCart\REST\Tax
 */

namespace TitanCart\REST\Tax;

use TitanCart\Modules\Tax\TaxManager;
use TitanCart\Modules\Tax\TaxClass;
use TitanCart\Modules\Tax\TaxZone;
use TitanCart\Modules\Tax\TaxRate;
use TitanCart\Core\LogService;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

defined( 'ABSPATH' ) || exit;

class TaxController extends WP_REST_Controller {

    protected $namespace = 'titancart/v1';
    protected $rest_base = 'tax';

    private TaxManager $manager;
    private TaxClass   $class_repo;
    private TaxZone    $zone_repo;
    private TaxRate    $rate_repo;

    public function __construct() {
        $this->manager    = new TaxManager();
        $this->class_repo = new TaxClass();
        $this->zone_repo  = new TaxZone();
        $this->rate_repo  = new TaxRate();
    }

    // ══════════════════════════════════════════════════════════════════════════
    // ROUTE REGISTRATION
    // ══════════════════════════════════════════════════════════════════════════

    public function register_routes(): void {

        // ── Tax Classes ───────────────────────────────────────────────────────

        register_rest_route( $this->namespace, "/{$this->rest_base}/classes", [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_classes' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_class' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, "/{$this->rest_base}/classes/(?P<id>\d+)", [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_class' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'update_class' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'disable_class' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // ── Tax Zones ─────────────────────────────────────────────────────────

        register_rest_route( $this->namespace, "/{$this->rest_base}/zones", [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_zones' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_zone' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, "/{$this->rest_base}/zones/(?P<id>\d+)", [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_zone' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'update_zone' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'delete_zone' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, "/{$this->rest_base}/zones/(?P<id>\d+)/locations", [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'add_location' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, "/{$this->rest_base}/zones/(?P<id>\d+)/locations/(?P<loc_id>\d+)", [
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'remove_location' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // ── Tax Rates ─────────────────────────────────────────────────────────

        register_rest_route( $this->namespace, "/{$this->rest_base}/rates", [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_rates' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_rate' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, "/{$this->rest_base}/rates/(?P<id>\d+)", [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_rate' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [ $this, 'update_rate' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [ $this, 'delete_rate' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        // ── Public checkout tax calculation ───────────────────────────────────
        // WP.org R1 🅱4: public by design — guest checkout computes tax for the
        // CALLER'S OWN cart session before an account exists. Pure calculation,
        // no state written, nothing customer-identifying returned.

        register_rest_route( $this->namespace, '/checkout/tax', [
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'calculate_checkout_tax' ],
                'permission_callback' => '__return_true', // Public by design: guest checkout — pure tax calculation for the caller's own cart session; writes nothing.
            ],
        ] );

        // ── Flat aliases (tester / external compat) ───────────────────────────

        register_rest_route( $this->namespace, '/tax-classes', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_classes' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_class' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, '/tax-zones', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_zones' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_zone' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );

        register_rest_route( $this->namespace, '/tax-rates', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [ $this, 'get_rates' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [ $this, 'create_rate' ],
                'permission_callback' => [ $this, 'admin_permission' ],
            ],
        ] );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // TAX CLASS HANDLERS
    // ══════════════════════════════════════════════════════════════════════════

    /** GET /tax/classes */
    public function get_classes( WP_REST_Request $request ): WP_REST_Response {
        $lang = sanitize_text_field( $request->get_param( 'lang' ) ?? 'en' );
        return new WP_REST_Response( $this->class_repo->get_all( $lang ), 200 );
    }

    /** POST /tax/classes */
    public function create_class( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $handle = sanitize_key( $request->get_param( 'handle' ) ?? '' );
        $name   = sanitize_text_field( $request->get_param( 'name' ) ?? '' );

        // Auto-generate handle from name when caller omits it.
        if ( $handle === '' && $name !== '' ) {
            $handle = sanitize_key( sanitize_title( $name ) );
        }

        if ( $handle === '' ) {
            return new WP_Error( 'tc_missing_handle', __( 'handle or name is required.', 'titancart-lite' ), [ 'status' => 422 ] );
        }

        // Prevent duplicate handles.
        $existing = $this->class_repo->get_by_handle( $handle );
        if ( $existing ) {
            return new WP_Error( 'tc_duplicate_handle', __( 'A tax class with this handle already exists.', 'titancart-lite' ), [ 'status' => 409 ] );
        }

        $translations = $this->extract_translations( $request, $name ?: $handle );
        $id           = $this->class_repo->create( [
            'handle'              => $handle,
            'status'              => sanitize_text_field( $request->get_param( 'status' ) ?? 'active' ),
            'registration_number' => sanitize_text_field( $request->get_param( 'registration_number' ) ?? '' ),
            'priority'            => (int) ( $request->get_param( 'priority' ) ?? 0 ),
            'rates_depend_on'     => sanitize_text_field( $request->get_param( 'rates_depend_on' ) ?? 'shipping' ),
            'price_includes_tax'  => ! empty( $request->get_param( 'price_includes_tax' ) ) ? 1 : 0,
        ], $translations );

        if ( ! $id ) {
            return new WP_Error( 'tc_create_failed', __( 'Failed to create tax class.', 'titancart-lite' ), [ 'status' => 500 ] );
        }

        LogService::info( 'general', 'create', 'Tax class created: ' . ( $name ?: $handle ) . ' (#' . $id . ')', [
            'object_type' => 'tax_class',
            'object_id'   => $id,
            'handle'      => $handle,
        ] );

        return new WP_REST_Response( $this->class_repo->get_by_id( $id ), 201 );
    }

    /** GET /tax/classes/{id} */
    public function get_class( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $class = $this->class_repo->get_by_id( (int) $request['id'] );

        if ( ! $class ) {
            return new WP_Error( 'tc_class_not_found', __( 'Tax class not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        return new WP_REST_Response( $class, 200 );
    }

    /** PUT /tax/classes/{id} */
    public function update_class( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id    = (int) $request['id'];
        $class = $this->class_repo->get_by_id( $id );

        if ( ! $class ) {
            return new WP_Error( 'tc_class_not_found', __( 'Tax class not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $data = array_filter( [
            'handle'              => $request->get_param( 'handle' ) !== null ? sanitize_key( $request->get_param( 'handle' ) ) : null,
            'status'              => $request->get_param( 'status' ),
            'registration_number' => $request->get_param( 'registration_number' ) !== null ? sanitize_text_field( $request->get_param( 'registration_number' ) ) : null,
            'priority'            => $request->get_param( 'priority' ) !== null ? (int) $request->get_param( 'priority' ) : null,
            'rates_depend_on'     => $request->get_param( 'rates_depend_on' ),
            'price_includes_tax'  => $request->get_param( 'price_includes_tax' ) !== null ? ( ! empty( $request->get_param( 'price_includes_tax' ) ) ? 1 : 0 ) : null,
        ], fn( $v ) => $v !== null );

        $translations = [];
        $raw = $request->get_param( 'translations' );
        if ( is_array( $raw ) ) {
            $translations = $raw;
        } elseif ( $request->get_param( 'name' ) !== null ) {
            $translations = [ 'en' => [ 'name' => sanitize_text_field( $request->get_param( 'name' ) ) ] ];
        }

        $this->class_repo->update( $id, $data, $translations );

        LogService::info( 'general', 'update', 'Tax class updated: #' . $id, [
            'object_type' => 'tax_class',
            'object_id'   => $id,
        ] );

        return new WP_REST_Response( $this->class_repo->get_by_id( $id ), 200 );
    }

    /** DELETE /tax/classes/{id} */
    public function disable_class( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id    = (int) $request['id'];
        $class = $this->class_repo->get_by_id( $id );

        if ( ! $class ) {
            return new WP_Error( 'tc_class_not_found', __( 'Tax class not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $this->class_repo->disable( $id );

        LogService::info( 'general', 'delete', 'Tax class disabled: #' . $id, [
            'object_type' => 'tax_class',
            'object_id'   => $id,
        ] );

        return new WP_REST_Response( [ 'disabled' => true, 'id' => $id ], 200 );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // TAX ZONE HANDLERS
    // ══════════════════════════════════════════════════════════════════════════

    /** GET /tax/zones */
    public function get_zones( WP_REST_Request $request ): WP_REST_Response {
        return new WP_REST_Response( $this->zone_repo->get_all(), 200 );
    }

    /** POST /tax/zones */
    public function create_zone( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $name = sanitize_text_field( $request->get_param( 'name' ) ?? '' );

        if ( $name === '' ) {
            return new WP_Error( 'tc_missing_name', __( 'Zone name is required.', 'titancart-lite' ), [ 'status' => 422 ] );
        }

        $id = $this->zone_repo->create( [ 'name' => $name, 'status' => 'active' ] );

        // KI-89: report a failed INSERT instead of returning 201 with a null body.
        // TaxZone::create() (L62-73) returns lastInsertId(), which wpdb zeroes on a
        // failed insert (class-wpdb.php L2296-2300), so 0 means "the row was not
        // written". Unchecked, get_by_id(0) returns null and WP core's REST server
        // (class-wp-rest-server.php L536-538) flattens a null result to a ZERO-BYTE
        // body with the status untouched — i.e. a 201 that created nothing.
        // Mirrors create_class() L282-284, ten lines up in this same file.
        if ( ! $id ) {
            return new WP_Error( 'tc_create_failed', __( 'Failed to create tax zone.', 'titancart-lite' ), [ 'status' => 500 ] );
        }

        $zone = $this->zone_repo->get_by_id( $id );
        return new WP_REST_Response( $zone, 201 );
    }

    /** GET /tax/zones/{id} */
    public function get_zone( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $zone = $this->zone_repo->get_by_id( (int) $request['id'] );

        if ( ! $zone ) {
            return new WP_Error( 'tc_zone_not_found', __( 'Tax zone not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        return new WP_REST_Response( $zone, 200 );
    }

    /** PUT /tax/zones/{id} */
    public function update_zone( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id   = (int) $request['id'];
        $zone = $this->zone_repo->get_by_id( $id );

        if ( ! $zone ) {
            return new WP_Error( 'tc_zone_not_found', __( 'Tax zone not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $data = array_filter( [
            'name'   => $request->get_param( 'name' )   !== null ? sanitize_text_field( $request->get_param( 'name' ) ) : null,
            'status' => $request->get_param( 'status' ),
        ], fn( $v ) => $v !== null );

        $this->zone_repo->update( $id, $data );
        return new WP_REST_Response( $this->zone_repo->get_by_id( $id ), 200 );
    }

    /** DELETE /tax/zones/{id} */
    public function delete_zone( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id   = (int) $request['id'];
        $zone = $this->zone_repo->get_by_id( $id );

        if ( ! $zone ) {
            return new WP_Error( 'tc_zone_not_found', __( 'Tax zone not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $this->zone_repo->delete( $id );
        return new WP_REST_Response( [ 'deleted' => true, 'id' => $id ], 200 );
    }

    /** POST /tax/zones/{id}/locations */
    public function add_location( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $zone_id = (int) $request['id'];

        if ( ! $this->zone_repo->get_by_id( $zone_id ) ) {
            return new WP_Error( 'tc_zone_not_found', __( 'Tax zone not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $type = $request->get_param( 'type' ) ?? 'country';
        $code = sanitize_text_field( $request->get_param( 'code' ) ?? '' );

        if ( $code === '' ) {
            return new WP_Error( 'tc_missing_code', __( 'Location code is required.', 'titancart-lite' ), [ 'status' => 422 ] );
        }

        if ( ! in_array( $type, [ 'country', 'state', 'postcode' ], true ) ) {
            return new WP_Error( 'tc_invalid_type', __( 'Type must be country, state, or postcode.', 'titancart-lite' ), [ 'status' => 422 ] );
        }

        $loc_id = $this->zone_repo->add_location( $zone_id, $type, $code );
        return new WP_REST_Response( [ 'id' => $loc_id, 'zone_id' => $zone_id, 'type' => $type, 'code' => strtoupper( $code ) ], 201 );
    }

    /** DELETE /tax/zones/{id}/locations/{loc_id} */
    public function remove_location( WP_REST_Request $request ): WP_REST_Response {
        $loc_id = (int) $request['loc_id'];
        $this->zone_repo->remove_location( $loc_id );
        return new WP_REST_Response( [ 'deleted' => true, 'id' => $loc_id ], 200 );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // TAX RATE HANDLERS
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * GET /tax/rates
     * Optional query params: class_id, zone_id
     */
    public function get_rates( WP_REST_Request $request ): WP_REST_Response {
        $class_id = $request->get_param( 'class_id' ) !== null ? (int) $request->get_param( 'class_id' ) : null;
        $zone_id  = $request->get_param( 'zone_id' )  !== null ? (int) $request->get_param( 'zone_id' )  : null;

        return new WP_REST_Response( $this->rate_repo->get_all( $class_id, $zone_id ), 200 );
    }

    /** POST /tax/rates */
    public function create_rate( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $class_id     = (int) ( $request->get_param( 'class_id' ) ?? 0 );
        $zone_id      = (int) ( $request->get_param( 'zone_id' )  ?? 0 );
        $country_code = strtoupper( sanitize_text_field( $request->get_param( 'country_code' ) ?? '' ) );
        $state_code   = strtoupper( sanitize_text_field( $request->get_param( 'state_code' ) ?? '' ) );
        $rate_name    = sanitize_text_field( $request->get_param( 'name' ) ?? '' );
        $rate         = $request->get_param( 'rate' );

        if ( $class_id <= 0 ) {
            return new WP_Error( 'tc_missing_class', __( 'class_id is required.', 'titancart-lite' ), [ 'status' => 422 ] );
        }
        if ( ! $this->class_repo->get_by_id( $class_id ) ) {
            return new WP_Error( 'tc_class_not_found', __( 'Tax class not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        // Auto-find or create zone from country_code + state_code if zone_id not provided.
        if ( $zone_id <= 0 && ( $country_code !== '' || $rate_name !== '' ) ) {
            $existing_zones = $this->zone_repo->get_all();

            if ( $state_code !== '' && $country_code !== '' ) {
                // ── State-level zone: look for zone with this state location ──
                $state_key = $country_code . ':' . $state_code;
                foreach ( $existing_zones as $z ) {
                    $locs = $z['locations'] ?? [];
                    foreach ( $locs as $loc ) {
                        if ( $loc['type'] === 'state' && strtoupper( $loc['code'] ) === $state_key ) {
                            $zone_id = (int) $z['id'];
                            break 2;
                        }
                    }
                }
                // Create state zone if not found.
                if ( $zone_id <= 0 ) {
                    $zone_name = $rate_name ?: $state_code . ', ' . $country_code;
                    $zone_id   = $this->zone_repo->create( [ 'name' => $zone_name, 'status' => 'active' ] );
                    // State-level zones get a state location only — the state
                    // code (e.g. "US:PA") already encodes the country, and a
                    // redundant country row would cause the matcher to also
                    // classify every non-PA US address as a country-level hit
                    // on this zone, applying the rate to all 50 states.
                    $this->zone_repo->add_location( $zone_id, 'state', $state_key );
                }
            } elseif ( $country_code !== '' ) {
                // ── Country-level zone ──
                foreach ( $existing_zones as $z ) {
                    $locs = $z['locations'] ?? [];
                    if ( count( $locs ) === 1
                         && $locs[0]['type'] === 'country'
                         && strtoupper( $locs[0]['code'] ) === $country_code ) {
                        $zone_id = (int) $z['id'];
                        break;
                    }
                }
                if ( $zone_id <= 0 ) {
                    $zone_name = $rate_name ?: $country_code;
                    $zone_id   = $this->zone_repo->create( [ 'name' => $zone_name, 'status' => 'active' ] );
                    $this->zone_repo->add_location( $zone_id, 'country', $country_code );
                }
            } else {
                // ── Default rate area (all countries) — zone with no locations ──
                foreach ( $existing_zones as $z ) {
                    $locs = $z['locations'] ?? [];
                    if ( empty( $locs ) ) {
                        $zone_id = (int) $z['id'];
                        break;
                    }
                }
                if ( $zone_id <= 0 ) {
                    $zone_name = $rate_name ?: 'Default rate area (all countries)';
                    $zone_id   = $this->zone_repo->create( [ 'name' => $zone_name, 'status' => 'active' ] );
                }
            }
        }

        if ( $zone_id <= 0 ) {
            return new WP_Error( 'tc_missing_zone', __( 'zone_id, country_code, or name is required.', 'titancart-lite' ), [ 'status' => 422 ] );
        }
        if ( ! $this->zone_repo->get_by_id( $zone_id ) ) {
            return new WP_Error( 'tc_zone_not_found', __( 'Tax zone not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $id = $this->rate_repo->create( [
            'class_id'    => $class_id,
            'zone_id'     => $zone_id,
            'rate'        => $rate ?? 0,
            'is_compound' => (int) ( $request->get_param( 'is_compound' ) ?? 0 ),
            'priority'    => (int) ( $request->get_param( 'priority' )    ?? 1 ),
            'includes_tax'=> (int) ( $request->get_param( 'includes_tax' )  ?? 0 ),
        ] );

        // Return the rate with zone info for the UI.
        $new_rate = $this->rate_repo->get_by_id( $id );
        if ( $new_rate ) {
            $zone = $this->zone_repo->get_by_id( (int) $new_rate['zone_id'] );
            $new_rate['zone_name'] = $zone ? $zone['name'] : '';
        }

        return new WP_REST_Response( $new_rate, 201 );
    }

    /** GET /tax/rates/{id} */
    public function get_rate( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $rate = $this->rate_repo->get_by_id( (int) $request['id'] );

        if ( ! $rate ) {
            return new WP_Error( 'tc_rate_not_found', __( 'Tax rate not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        return new WP_REST_Response( $rate, 200 );
    }

    /** PUT /tax/rates/{id} */
    public function update_rate( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id   = (int) $request['id'];
        $rate = $this->rate_repo->get_by_id( $id );

        if ( ! $rate ) {
            return new WP_Error( 'tc_rate_not_found', __( 'Tax rate not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $data = array_filter( [
            'class_id'     => $request->get_param( 'class_id' )     !== null ? (int) $request->get_param( 'class_id' )     : null,
            'zone_id'      => $request->get_param( 'zone_id' )      !== null ? (int) $request->get_param( 'zone_id' )      : null,
            'rate'         => $request->get_param( 'rate' ),
            'is_compound'  => $request->get_param( 'is_compound' )  !== null ? (int) $request->get_param( 'is_compound' )  : null,
            'priority'     => $request->get_param( 'priority' )     !== null ? (int) $request->get_param( 'priority' )     : null,
            'includes_tax' => $request->get_param( 'includes_tax' ) !== null ? (int) $request->get_param( 'includes_tax' ) : null,
        ], fn( $v ) => $v !== null );

        $this->rate_repo->update( $id, $data );
        return new WP_REST_Response( $this->rate_repo->get_by_id( $id ), 200 );
    }

    /** DELETE /tax/rates/{id} */
    public function delete_rate( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $id   = (int) $request['id'];
        $rate = $this->rate_repo->get_by_id( $id );

        if ( ! $rate ) {
            return new WP_Error( 'tc_rate_not_found', __( 'Tax rate not found.', 'titancart-lite' ), [ 'status' => 404 ] );
        }

        $this->rate_repo->delete( $id );
        return new WP_REST_Response( [ 'deleted' => true, 'id' => $id ], 200 );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // PUBLIC CHECKOUT TAX CALCULATION
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * POST /checkout/tax
     *
     * Request body:
     *   {
     *     "address": { "country": "US", "state": "CA", "postcode": "90210" },
     *     "cart":    { "items": [{product_id, quantity, unit_price | price}, ...] }   // optional
     *   }
     *
     * If `cart` is omitted, the active session cart is loaded server-side —
     * same pattern as /checkout/shipping-methods.  External callers (testers,
     * integrators) can still pass an explicit cart for one-off calculations.
     *
     * Response:
     *   {
     *     "tax_total":  12.50,
     *     "breakdown":  [ {class_id, class_name, rate_percent, tax_amount, includes_tax}, ... ]
     *   }
     */
    public function calculate_checkout_tax( WP_REST_Request $request ): WP_REST_Response|WP_Error {
        $address = $request->get_param( 'address' );

        if ( ! is_array( $address ) ) {
            return new WP_Error(
                'tc_missing_address',
                __( 'address is required.', 'titancart-lite' ),
                [ 'status' => 422 ]
            );
        }

        if ( empty( $address['country'] ) ) {
            return new WP_Error(
                'tc_missing_country',
                __( 'address.country is required.', 'titancart-lite' ),
                [ 'status' => 422 ]
            );
        }

        // Cart can be passed explicitly (testers / external callers) or loaded
        // from the active session — same pattern as /checkout/shipping-methods.
        $cart = $request->get_param( 'cart' );
        if ( ! is_array( $cart ) ) {
            $cart_manager = new \TitanCart\Modules\Cart\CartManager();
            $cart         = $cart_manager->get_cart();
        }

        // Empty cart → zero tax, no error.  Lets the storefront keep the
        // tax row visible at $0.00 before any items are in the cart.
        if ( empty( $cart['items'] ) ) {
            return new WP_REST_Response( [
                'tax_total' => 0.0,
                'breakdown' => [],
            ], 200 );
        }

        $breakdown = $this->manager->get_rate_breakdown( $cart, $address );
        $total     = array_sum( array_column( $breakdown, 'tax_amount' ) );

        return new WP_REST_Response( [
            'tax_total' => round( (float) $total, 2 ),
            'breakdown' => $breakdown,
        ], 200 );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // PERMISSIONS
    // ══════════════════════════════════════════════════════════════════════════

    public function admin_permission(): bool|WP_Error {
        if ( current_user_can( 'manage_options' ) ) {
            return true;
        }
        return new WP_Error(
            'tc_rest_forbidden',
            __( 'You do not have permission to access this resource.', 'titancart-lite' ),
            [ 'status' => 403 ]
        );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // INTERNAL HELPERS
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * Extract translations from request — same pattern as ShippingController.
     */
    private function extract_translations( WP_REST_Request $request, string $fallback_name ): array {
        $raw = $request->get_param( 'translations' );

        if ( is_array( $raw ) ) {
            return $raw;
        }

        return [
            'en' => [
                'name' => $fallback_name,
            ],
        ];
    }
}
