<?php

namespace PortalPeople\shippingcosts\models;

use yii\base\Model;

/**
 * ColumnMapForm
 *
 * Holds the user's mapping of each spreadsheet header to a Shippingcosts field role.
 * Provides auto-detection heuristics and price-header parsing.
 *
 * Roles:
 *   ignore    – column is skipped
 *   postcodes – value → Shippingcosts::$postcodes
 *   haulierid – value → Shippingcosts::$haulierid (name or intid)
 *   type      – value → Shippingcosts::$type
 *   notes     – value → Shippingcosts::$notes
 *   price     – column is a price band; header is parsed for size/qty; cell value is the price
 */
class ColumnMapForm extends Model
{
    const ROLE_IGNORE    = 'ignore';
    const ROLE_POSTCODES = 'postcodes';
    const ROLE_HAULIER   = 'haulierid';
    const ROLE_TYPE      = 'type';
    const ROLE_NOTES     = 'notes';
    const ROLE_PRICE     = 'price';

    /**
     * Normalise raw type values from the spreadsheet to canonical DB values.
     * Keys are lowercase trimmed versions of the spreadsheet cell value.
     */
    const TYPE_MAP = [
        'econ' => 'Eco',
        'prem' => 'Next day',
    ];

    /**
     * Mapping of size keyword (lowercase) → numeric multiplier stored on the price row label.
     * e.g. "Qtr 1 X" → size = 0.25
     */
    const SIZE_MAP = [
        'qtr'     => 0.25,
        'quarter' => 0.25,
        'half'    => 0.50,
        'full'    => 1.00,
    ];

    /** @var string[] Spreadsheet headers in column order */
    public array $headers = [];

    /** @var array<string, string>  header → role */
    public array $assignments = [];

    public function rules(): array
    {
        return [
            [['headers'],     'safe'],
            [['assignments'], 'safe'],
            [['assignments'], 'validateHasPostcodes'],
        ];
    }

    public function validateHasPostcodes(string $attribute): void
    {
        if (!in_array(self::ROLE_POSTCODES, $this->assignments, true)) {
            $this->addError($attribute, 'At least one column must be assigned the "Postcodes" role.');
        }
    }

    public static function roleOptions(): array
    {
        return [
            self::ROLE_IGNORE    => 'Ignore',
            self::ROLE_POSTCODES => 'Postcodes',
            self::ROLE_HAULIER   => 'Haulier',
            self::ROLE_TYPE      => 'Service Type',
            self::ROLE_NOTES     => 'Notes',
            self::ROLE_PRICE     => 'Price Column',
        ];
    }

    /**
     * Populate $assignments with best-guess roles based on header names.
     * Called on GET before the form is shown.
     */
    public function autoAssign(): void
    {
        foreach ($this->headers as $header) {
            $this->assignments[$header] = $this->guessRole((string) $header);
        }
    }

    private function guessRole(string $header): string
    {
        $lower = strtolower(trim($header));

        if (str_contains($lower, 'postcode') || str_contains($lower, 'post code') || $lower === 'postcode') {
            return self::ROLE_POSTCODES;
        }
        if (in_array($lower, ['provider', 'haulier', 'carrier', 'supplier'], true)) {
            return self::ROLE_HAULIER;
        }
        if (in_array($lower, ['service', 'type', 'service type', 'servicetype'], true)) {
            return self::ROLE_TYPE;
        }
        if (in_array($lower, ['notes', 'note', 'erpid', 'erp id', 'erp_id', 'description'], true)) {
            return self::ROLE_NOTES;
        }
        if ($this->looksLikePriceHeader($header)) {
            return self::ROLE_PRICE;
        }

        return self::ROLE_IGNORE;
    }

    private function looksLikePriceHeader(string $header): bool
    {
        $lower = strtolower($header);
        foreach (array_keys(self::SIZE_MAP) as $word) {
            if (str_contains($lower, $word)) {
                return true;
            }
        }
        // Matches "1 X", "2x", "3 X" patterns
        return (bool) preg_match('/\b\d+\s*[xX]\b/', $header);
    }

    /**
     * Parse a price-column header to extract label, size multiplier, and qty.
     *
     * Examples:
     *   "Qtr 1 X"  → ['label' => 'Qtr 1 X',  'size' => 0.25, 'qty' => 1]
     *   "Half 2 X" → ['label' => 'Half 2 X', 'size' => 0.50, 'qty' => 2]
     *   "Full 3 X" → ['label' => 'Full 3 X', 'size' => 1.00, 'qty' => 3]
     *
     * @return array{label: string, size: float|null, qty: int|null}
     */
    public static function parsePriceHeader(string $header): array
    {
        $size = null;
        $qty  = null;

        // Match size keyword
        foreach (self::SIZE_MAP as $word => $value) {
            if (preg_match('/\b' . preg_quote($word, '/') . '\b/i', $header)) {
                $size = $value;
                break;
            }
        }

        // Match qty from "{digits} X" pattern  e.g. "1 X", "2x"
        if (preg_match('/\b(\d+)\s*[xX]\b/', $header, $m)) {
            $qty = (int) $m[1];
        }

        return [
            'label' => $header,
            'size'  => $size,
            'qty'   => $qty,
        ];
    }
}
