<?php
/**
 * File Path: includes/api/esp/class-mbs-esp-flodesk.php
 *
 * Flodesk ESP Adapter -- My Book Showroom
 *
 * API: Flodesk API v1 (api.flodesk.com/v1).
 * Auth: HTTP Basic -- API key as username, empty password.
 * Lists: Flodesk calls them "segments". The adapter maps segment IDs to the
 *        generic list abstraction used throughout MBS.
 * Campaigns: Flodesk has no campaign creation/send API. supports_campaigns()
 *            returns false; the broadcast panel shows the manual-send advisory.
 * Tags: Implemented via segment assignment. Tag strings are matched to existing
 *       segment names (case-insensitive). Unmatched tags are skipped rather than
 *       erroring -- segments must be pre-created in the Flodesk dashboard.
 *
 * Sprint F-026c: Flodesk Adapter.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Api/Esp
 * @since      1.1.1
 */

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

class MBS_ESP_Flodesk extends MBS_ESP_Adapter_Base { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	protected const BASE_URL        = 'https://api.flodesk.com/v1/';

	/** @var string */
	private string $api_key;

	/** @var array<string,string>|null  Cached name -> id segment map. */
	private ?array $segment_map = null;

	public function __construct( string $api_key ) {
		$this->api_key = $api_key;
	}

	// -------------------------------------------------------------------------
	// Interface implementation
	// -------------------------------------------------------------------------

	/** {@inheritDoc} */
	public function connect(): bool {
		$response = $this->get( 'subscribers?limit=1' );
		if ( is_wp_error( $response ) ) {
			return false;
		}
		return 200 === (int) wp_remote_retrieve_response_code( $response );
	}

	/** {@inheritDoc} */
	public function get_lists(): array {
		$response = $this->get( 'segments' );
		if ( is_wp_error( $response ) ) {
			return array();
		}
		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( empty( $body['data'] ) || ! is_array( $body['data'] ) ) {
			return array();
		}
		$result = array();
		foreach ( $body['data'] as $segment ) {
			$result[] = array(
				'id'   => (string) ( $segment['id'] ?? '' ),
				'name' => (string) ( $segment['name'] ?? '' ),
			);
		}
		return $result;
	}

	/** {@inheritDoc} */
	public function subscribe( string $email, string $name, string $list_id, bool $double_optin ): bool {
		// Build subscriber payload.
		// Note: double_optin is governed by the segment's Flodesk dashboard setting,
		// not the API call. We pass the flag as advisory -- no per-request override exists.
		$payload = array( 'email' => $email );
		if ( '' !== $name ) {
			// Flodesk uses first_name / last_name. Use the full name as first_name
			// since MBS only collects a single name field.
			$payload['first_name'] = $name;
		}
		if ( '' !== $list_id ) {
			$payload['segment_ids'] = array( $list_id );
		}
		$response = $this->post( 'subscribers', $payload );
		if ( is_wp_error( $response ) ) {
			return false;
		}
		$code = (int) wp_remote_retrieve_response_code( $response );
		// 200 = updated, 201 = created. Both are success.
		return $code >= 200 && $code < 300;
	}

	/** {@inheritDoc} */
	public function tag_contact( string $email, array $tags, string $list_id ): bool {
		if ( empty( $tags ) ) {
			return true;
		}
		// Build a name -> id map from the account's segments.
		$map = $this->get_segment_map();
		if ( empty( $map ) ) {
			// No segments exist or API failed -- treat as a silent no-op, not an error.
			return true;
		}
		// Collect IDs for tags that match existing segment names.
		$segment_ids = array();
		foreach ( $tags as $tag ) {
			$key = strtolower( trim( (string) $tag ) );
			if ( isset( $map[ $key ] ) ) {
				$segment_ids[] = $map[ $key ];
			}
			// Unmatched tags are skipped -- segments must be pre-created in Flodesk.
		}
		if ( empty( $segment_ids ) ) {
			return true; // No matching segments -- not an error.
		}
		// Assign subscriber to the matched segments.
		$endpoint = 'subscribers/' . rawurlencode( $email ) . '/segments';
		$response = $this->post( $endpoint, array( 'segment_ids' => $segment_ids ) );
		if ( is_wp_error( $response ) ) {
			return false;
		}
		$code = (int) wp_remote_retrieve_response_code( $response );
		return $code >= 200 && $code < 300;
	}

	/** {@inheritDoc} */
	public function supports_tags(): bool {
		return true;
	}

	/**
	 * {@inheritDoc}
	 * Flodesk has no campaign creation or send API.
	 */
	public function create_campaign( string $subject, string $body_html, string $list_id ): ?string {
		return null;
	}

	/** {@inheritDoc} */
	public function send_campaign( string $campaign_id ): bool {
		return false;
	}

	/** {@inheritDoc} */
	public function supports_campaigns(): bool {
		return false;
	}

	// -------------------------------------------------------------------------
	// Private helpers
	// -------------------------------------------------------------------------

	/**
	 * Return a lowercased-name -> segment-id map for all account segments.
	 * Result is cached for the lifetime of the request.
	 *
	 * @return array<string,string>
	 */
	private function get_segment_map(): array {
		if ( null !== $this->segment_map ) {
			return $this->segment_map;
		}
		$this->segment_map = array();
		$lists = $this->get_lists();
		foreach ( $lists as $item ) {
			$key = strtolower( trim( $item['name'] ) );
			if ( '' !== $key && '' !== $item['id'] ) {
				$this->segment_map[ $key ] = $item['id'];
			}
		}
		return $this->segment_map;
	}

	/**
	 * HTTP Basic auth header.
	 * Flodesk uses API key as username with empty password.
	 *
	 * @return array<string,string>
	 */
	protected function auth_headers(): array {
		return array(
			'Authorization' => 'Basic ' . base64_encode( $this->api_key . ':' ),
		);
	}
}
