<?php

namespace helpers;

/**
 * Helper to normalize/booked invoice data from E-conomic (stdClass) to a PHP array structure
 * with snake_case keys and convenient totals.
 *
 * This helper also exposes documented object properties mirroring the most relevant
 * top-level fields from the E‑conomic booked invoice payload for easier IDE discovery.
 *
 * Notes on mapping (E‑conomic → helper):
 * - bookedInvoiceNumber → booked_invoice_number
 * - orderNumber → order_number
 * - date → date (Y-m-d)
 * - currency → currency (e.g. DKK)
 * - exchangeRate → exchange_rate
 * - netAmount → net_amount
 * - netAmountInBaseCurrency → net_amount_in_base_currency
 * - grossAmount → gross_amount (also exposed as total_amount)
 * - grossAmountInBaseCurrency → gross_amount_in_base_currency
 * - vatAmount → vat_amount
 * - roundingAmount → rounding_amount
 * - remainder → remainder
 * - remainderInBaseCurrency → remainder_in_base_currency
 * - dueDate → due_date (Y-m-d)
 * - paymentTerms → payment_terms (array)
 * - customer → customer (array)
 * - recipient → recipient (array with nested vat_zone)
 * - references → references (array, typically contains other)
 * - layout → layout (array)
 * - pdf → pdf (array with download)
 * - lines → lines (array of line arrays)
 * - sent → sent (string URL) if present
 * - self → self (string URL) if present
 *
 * Convenience fields:
 * - total_amount (float) mirrors gross_amount
 * - total_net_amount (float) mirrors net_amount
 */
class economic_invoice_booked
{
    /**
     * Raw booked invoice data as returned by the E-conomic API (stdClass)
     * @var object
     */
    protected object $booked_invoice_data;

    /**
     * Normalized array (snake_case keys)
     * @var array
     */
    protected array $normalized = [];

    /**
     * The booked invoice number in E‑conomic.
     * Origin: bookedInvoiceNumber
     * @var int
     */
    public int $booked_invoice_number = 0;

    /**
     * The order number associated with the invoice, if any.
     * Origin: orderNumber
     * @var int|null
     */
    public ?int $order_number = null;

    /**
     * Invoice booking date (YYYY-MM-DD).
     * Origin: date
     * @var string
     */
    public string $date = '';

    /**
     * Invoice currency code (e.g., DKK).
     * Origin: currency
     * @var string
     */
    public string $currency = '';

    /**
     * Exchange rate for the invoice currency relative to base currency.
     * Origin: exchangeRate
     * @var float
     */
    public float $exchange_rate = 1.0;

    /**
     * Net amount in invoice currency.
     * Origin: netAmount
     * @var float
     */
    public float $net_amount = 0.0;

    /**
     * Net amount in base currency.
     * Origin: netAmountInBaseCurrency
     * @var float
     */
    public float $net_amount_in_base_currency = 0.0;

    /**
     * Gross amount in invoice currency (includes VAT if applicable).
     * Origin: grossAmount
     * Also exposed as total_amount.
     * @var float
     */
    public float $gross_amount = 0.0;

    /**
     * Gross amount in base currency.
     * Origin: grossAmountInBaseCurrency
     * @var float
     */
    public float $gross_amount_in_base_currency = 0.0;

    /**
     * Total VAT amount for the invoice.
     * Origin: vatAmount
     * @var float
     */
    public float $vat_amount = 0.0;

    /**
     * Rounding adjustment amount.
     * Origin: roundingAmount
     * @var float
     */
    public float $rounding_amount = 0.0;

    /**
     * Remainder amount (outstanding) in invoice currency.
     * Origin: remainder
     * @var float
     */
    public float $remainder = 0.0;

    /**
     * Remainder amount (outstanding) in base currency.
     * Origin: remainderInBaseCurrency
     * @var float
     */
    public float $remainder_in_base_currency = 0.0;

    /**
     * Invoice due date (YYYY-MM-DD) when applicable.
     * Origin: dueDate
     * @var string|null
     */
    public ?string $due_date = null;

    /**
     * Payment terms info.
     * Origin: paymentTerms
     * @var array{payment_terms_number?:int,days_of_credit?:int,name?:string,payment_terms_type?:string,self?:string}
     */
    public array $payment_terms = [];

    /**
     * Customer reference.
     * Origin: customer
     * @var array{customer_number?:int,self?:string}
     */
    public array $customer = [];

    /**
     * Recipient details and VAT zone.
     * Origin: recipient
     * @var array{name?:string,address?:string,zip?:string|int,city?:string,vat_zone?:array{name?:string,vat_zone_number?:int,enabled_for_customer?:int|bool,enabled_for_supplier?:int|bool,self?:string}}
     */
    public array $recipient = [];

    /**
     * References (e.g., other/external id).
     * Origin: references
     * @var array{other?:string}
     */
    public array $references = [];

    /**
     * Layout reference used for the invoice.
     * Origin: layout
     * @var array{layout_number?:int,self?:string}
     */
    public array $layout = [];

    /**
     * PDF info (download link).
     * Origin: pdf
     * @var array{download?:string}
     */
    public array $pdf = [];

    /**
     * Invoice lines (normalized to snake_case keys).
     * Origin: lines
     * @var array<int, array{
     *   line_number?:int,sort_key?:int,description?:string,
     *   quantity?:int|float,unit_net_price?:int|float,discount_percentage?:int|float,
     *   unit_cost_price?:int|float,vat_rate?:int|float,vat_amount?:int|float,total_net_amount?:int|float,
     *   product?:array{product_number?:int|string,self?:string},
     *   departmental_distribution?:array{
     *     departmental_distribution_number?:int,name?:string,barred?:bool|int|null,distribution_type?:string,
     *     distributions?:array<int, array{percentage?:int|float,department?:array{department_number?:int,self?:string}}>,
     *     self?:string
     *   }
     * }>
     */
    public array $lines = [];

    /**
     * Link to E‑conomic API endpoint indicating sent status (if present).
     * Origin: sent
     * @var string|null
     */
    public ?string $sent = null;

    /**
     * Self link to this resource in E‑conomic.
     * Origin: self
     * @var string|null
     */
    public ?string $self = null;

    /**
     * Convenience: mirrors gross_amount.
     * @var float
     */
    public float $total_amount = 0.0;

    /**
     * Convenience: mirrors net_amount.
     * @var float
     */
    public float $total_net_amount = 0.0;

    public function __construct(object $booked_invoice_data)
    {
        $this->booked_invoice_data = $booked_invoice_data;
        // Prepare normalized structure immediately
        $this->normalized = $this->normalize($this->booked_invoice_data);
        // Provide convenience totals expected by internal callers
        if (!isset($this->normalized['total_amount']) && isset($this->normalized['gross_amount'])) {
            $this->normalized['total_amount'] = (float)$this->normalized['gross_amount'];
        }
        if (!isset($this->normalized['total_net_amount']) && isset($this->normalized['net_amount'])) {
            $this->normalized['total_net_amount'] = (float)$this->normalized['net_amount'];
        }
        // Populate documented properties for IDE discoverability
        $this->populatePropertiesFromArray($this->normalized);
    }

    /**
     * Convert the booked invoice stdClass into a normalized array (snake_case keys)
     * and add convenience fields like total_amount and total_net_amount.
     */
    public function toArray(): array
    {
        return $this->normalized;
    }

    /**
     * Recursively normalize stdClass/array values to arrays with snake_case keys
     * while preserving scalars.
     * @param mixed $value
     * @return mixed
     */
    protected function normalize($value)
    {
        if (is_object($value)) {
            // Convert stdClass to assoc array with snake_case keys
            $result = [];
            foreach (get_object_vars($value) as $k => $v) {
                $key = $this->camelToSnake((string)$k);
                $result[$key] = $this->normalize($v);
            }
            return $result;
        }
        if (is_array($value)) {
            $result = [];
            foreach ($value as $k => $v) {
                // Preserve numeric array indexes; convert associative keys to snake_case
                if (is_string($k)) {
                    $key = $this->camelToSnake($k);
                    $result[$key] = $this->normalize($v);
                } else {
                    $result[$k] = $this->normalize($v);
                }
            }
            return $result;
        }
        // Scalars: cast numeric strings to appropriate numeric types when reasonable
        if (is_string($value)) {
            if (is_numeric($value)) {
                // Decide int vs float based on presence of decimal point
                return (strpos($value, '.') !== false) ? (float)$value : (int)$value;
            }
            return $value;
        }
        return $value;
    }

    private function camelToSnake(string $input): string
    {
        // Preserve common abbreviations and already snake keys
        if (strpos($input, '_') !== false) {
            return strtolower($input);
        }
        $snake = preg_replace('/(?<!^)[A-Z]/', '_$0', $input);
        return strtolower($snake);
    }

    /**
     * Populate documented public properties from the normalized array.
     */
    private function populatePropertiesFromArray(array $data): void
    {
        $this->booked_invoice_number = (int)($data['booked_invoice_number'] ?? 0);
        $this->order_number = isset($data['order_number']) ? (int)$data['order_number'] : null;
        $this->date = (string)($data['date'] ?? '');
        $this->currency = (string)($data['currency'] ?? '');
        $this->exchange_rate = (float)($data['exchange_rate'] ?? 1.0);
        $this->net_amount = (float)($data['net_amount'] ?? 0.0);
        $this->net_amount_in_base_currency = (float)($data['net_amount_in_base_currency'] ?? 0.0);
        $this->gross_amount = (float)($data['gross_amount'] ?? 0.0);
        $this->gross_amount_in_base_currency = (float)($data['gross_amount_in_base_currency'] ?? 0.0);
        $this->vat_amount = (float)($data['vat_amount'] ?? 0.0);
        $this->rounding_amount = (float)($data['rounding_amount'] ?? 0.0);
        $this->remainder = (float)($data['remainder'] ?? 0.0);
        $this->remainder_in_base_currency = (float)($data['remainder_in_base_currency'] ?? 0.0);
        $this->due_date = isset($data['due_date']) && $data['due_date'] !== '' ? (string)$data['due_date'] : null;
        $this->payment_terms = isset($data['payment_terms']) && is_array($data['payment_terms']) ? $data['payment_terms'] : [];
        $this->customer = isset($data['customer']) && is_array($data['customer']) ? $data['customer'] : [];
        $this->recipient = isset($data['recipient']) && is_array($data['recipient']) ? $data['recipient'] : [];
        $this->references = isset($data['references']) && is_array($data['references']) ? $data['references'] : [];
        $this->layout = isset($data['layout']) && is_array($data['layout']) ? $data['layout'] : [];
        $this->pdf = isset($data['pdf']) && is_array($data['pdf']) ? $data['pdf'] : [];
        $this->lines = isset($data['lines']) && is_array($data['lines']) ? $data['lines'] : [];
        $this->sent = isset($data['sent']) ? (string)$data['sent'] : null;
        $this->self = isset($data['self']) ? (string)$data['self'] : null;
        $this->total_amount = (float)($data['total_amount'] ?? $this->gross_amount);
        $this->total_net_amount = (float)($data['total_net_amount'] ?? $this->net_amount);
    }
}
