<?php

declare(strict_types=1);

namespace App\Http\Requests\Asset;

use App\Enums\AssetStatus;
use Illuminate\Foundation\Http\FormRequest;

/**
 * Normalisation shared by the store and update asset requests.
 *
 * `status` is deliberately left untouched here: Store restricts it to
 * {@see AssetStatus::assignableOnCreate()} and Update excludes it
 * entirely (see AssetService::update()) — a single shared rule would blur that
 * distinction.
 */
final class AssetRules
{
    /**
     * @return array<string, mixed>
     */
    public static function normalise(FormRequest $request): array
    {
        $categoryId = $request->input('category_id');
        $locationId = $request->input('location_id');
        $teamId = $request->input('team_id');
        $currency = trim((string) $request->input('currency'));

        return [
            'asset_tag' => self::normaliseTag($request->input('asset_tag')),
            'name' => trim((string) $request->input('name')),
            'serial_number' => self::nullableString($request->input('serial_number')),
            'mac_address' => self::normaliseMacAddress($request->input('mac_address')),
            'category_id' => ($categoryId === null || $categoryId === '') ? null : (int) $categoryId,
            'manufacturer' => self::nullableString($request->input('manufacturer')),
            'model' => self::nullableString($request->input('model')),
            'vendor' => self::nullableString($request->input('vendor')),
            'location_id' => ($locationId === null || $locationId === '') ? null : (int) $locationId,
            'team_id' => ($teamId === null || $teamId === '') ? null : (int) $teamId,
            'condition' => trim((string) $request->input('condition')),
            'purchase_date' => self::nullableString($request->input('purchase_date')),
            'purchase_cost' => self::nullableString($request->input('purchase_cost')),
            'currency' => $currency === '' ? (string) setting('default_currency', 'INR') : mb_strtoupper($currency),
            'invoice_number' => self::nullableString($request->input('invoice_number')),
            'po_number' => self::nullableString($request->input('po_number')),
            'notes' => self::nullableString($request->input('notes')),
            'is_active' => $request->boolean('is_active', true),
            'specifications' => self::specifications($request),
            // Only meaningful on the create form (see StoreAssetRequest);
            // harmless here since UpdateAssetRequest never declares a rule
            // for these keys, so FormRequest::validated() drops them.
            'warranty_start_date' => self::nullableString($request->input('warranty_start_date')),
            'warranty_end_date' => self::nullableString($request->input('warranty_end_date')),
        ];
    }

    /**
     * No format enforced deliberately — just trimmed and uppercased for
     * consistent uniqueness (so "aa11.." and "AA11.." can't both exist as
     * "different" addresses). Whatever separators or lack of them someone
     * types — colons, dashes, none — are kept exactly as typed.
     *
     * Public: ImportService::validateRow() reuses this exact normaliser for
     * spreadsheet rows, so the same value typed in a form and in an import
     * both land in the database the same way.
     */
    public static function normaliseMacAddress(mixed $value): ?string
    {
        $value = mb_strtoupper(trim((string) $value));

        return $value === '' ? null : $value;
    }

    /**
     * Uppercased and trimmed, matching how a generated tag already looks
     * (e.g. "AUM-LAP-0042"). Left blank falls through to auto-generation in
     * AssetService::create() — this never invents a value.
     *
     * Public: AssetImportModule::validateRow() reuses this exact normaliser
     * so a tag typed in the form and one typed in a spreadsheet both land in
     * the database the same way.
     */
    public static function normaliseTag(mixed $value): ?string
    {
        $value = mb_strtoupper(trim((string) $value));

        return $value === '' ? null : $value;
    }

    /**
     * Zips the repeatable `spec_key[]` / `spec_value[]` rows from the
     * Technical Information fieldset into an associative array.
     *
     * Named-array inputs rather than a single hidden JSON field kept in sync
     * by JavaScript, because the form must still work with JavaScript
     * disabled: a fixed set of key/value rows submits correctly either way,
     * and JS only adds the convenience of inserting more rows.
     *
     * @return array<string, string>|null
     */
    private static function specifications(FormRequest $request): ?array
    {
        /** @var array<int, mixed> $keys */
        $keys = (array) $request->input('spec_key', []);
        /** @var array<int, mixed> $values */
        $values = (array) $request->input('spec_value', []);

        $specifications = [];

        foreach ($keys as $index => $key) {
            $key = trim((string) $key);
            $value = trim((string) ($values[$index] ?? ''));

            if ($key === '' || $value === '') {
                continue;
            }

            $specifications[$key] = $value;
        }

        return $specifications === [] ? null : $specifications;
    }

    private static function nullableString(mixed $value): ?string
    {
        $value = trim((string) $value);

        return $value === '' ? null : $value;
    }
}
