<?php

namespace Storeplugin\PDFInvoice\Framework\Abstracts;

use Storeplugin\PDFInvoice\Framework\Contracts\FieldInterface;
use Storeplugin\PDFInvoice\Framework\Utils\FieldState;

defined('ABSPATH') || exit;

abstract class AbstractField implements FieldInterface
{
    /**
     * Get the field name.
     *
     * @param string $id
     * @param string $prefix
     * @param bool   $isArray
     *
     * @return string
     */
    protected function getFieldName(string $id, string $prefix, bool $isArray = false): string
    {
        $name = sprintf('%s[%s]', $prefix, $id);
        return $isArray ? $name . '[]' : $name;
    }

    /**
     * Get the DOM id for the current render context.
     *
     * The logical field identifier remains in `$field['id']`; renderers should
     * use this helper whenever they need the HTML `id` / `for` target.
     *
     * @param array $field
     * @return string
     */
    protected function getFieldId(array $field): string
    {
        return (string) ($field['_render_id'] ?? $field['id'] ?? '');
    }

    /**
     * Build attributes.
     *
     * @param array $attrs
     *
     * @return string
     */
    protected function buildAttributes(array $attrs): string
    {
        $parts = [];

        foreach ($attrs as $attributeName => $attributeValue) {
            if ($attributeValue === null || $attributeValue === false) {
                continue;
            }
            if ($attributeValue === '' && $attributeName !== 'value') {
                continue;
            }

            if ($attributeValue === true) {
                $parts[] = \esc_attr($attributeName);
                continue;
            }

            $parts[] = sprintf('%s="%s"', \esc_attr($attributeName), \esc_attr($attributeValue));
        }

        return implode(' ', $parts);
    }

    /**
     * Normalize disabled option values into a string-keyed lookup.
     *
     * @param array $field
     * @return array<string, bool>
     */
    protected function getDisabledOptionLookup(array $field): array
    {
        $disabledOptions = $field['disabled_options'] ?? [];
        if (!is_array($disabledOptions) || $disabledOptions === []) {
            return [];
        }

        $lookup = [];
        foreach ($disabledOptions as $optionValue) {
            $lookup[(string) $optionValue] = true;
        }

        return $lookup;
    }

    /**
     * Encode disabled option values for client-side rehydration.
     *
     * @param array<string, bool> $disabledLookup
     * @return string|null
     */
    protected function getDisabledOptionsAttribute(array $disabledLookup): ?string
    {
        if ($disabledLookup === []) {
            return null;
        }

        $json = \wp_json_encode(array_keys($disabledLookup));

        return is_string($json) && $json !== '' ? $json : null;
    }

    /**
     * Normalize select-style option definitions.
     *
     * Supports both flat `value => label` pairs and richer option metadata.
     *
     * @param array $field
     * @return array<int, array{value:string,label:string,disabled:bool,title:string}>
     */
    protected function normalizeOptionDefinitions(array $field): array
    {
        $options = $field['options'] ?? [];
        if (!is_array($options) || $options === []) {
            return [];
        }

        $disabledLookup = $this->getDisabledOptionLookup($field);
        $normalized = [];

        foreach ($options as $optionValue => $optionDefinition) {
            $normalized[] = $this->normalizeOptionDefinition((string) $optionValue, $optionDefinition, $disabledLookup);
        }

        return $normalized;
    }

    /**
     * Normalize a single option definition.
     *
     * @param string              $optionValue
     * @param mixed               $optionDefinition
     * @param array<string, bool> $disabledLookup
     * @return array{value:string,label:string,disabled:bool,title:string}
     */
    protected function normalizeOptionDefinition(string $optionValue, $optionDefinition, array $disabledLookup = []): array
    {
        $label = is_scalar($optionDefinition) ? (string) $optionDefinition : $optionValue;
        $disabled = isset($disabledLookup[$optionValue]);
        $title = '';

        if (is_array($optionDefinition)) {
            if (array_key_exists('label', $optionDefinition) && is_scalar($optionDefinition['label'])) {
                $label = (string) $optionDefinition['label'];
            }

            if (array_key_exists('disabled', $optionDefinition)) {
                $disabled = $disabled || $this->isTruthy($optionDefinition['disabled']);
            }

            if (array_key_exists('title', $optionDefinition) && is_scalar($optionDefinition['title'])) {
                $title = (string) $optionDefinition['title'];
            }
        }

        return [
            'value' => $optionValue,
            'label' => $label,
            'disabled' => $disabled,
            'title' => $title,
        ];
    }

    /**
     * Get input classes.
     *
     * @return string
     */
    protected function getInputClasses(): string
    {
        return 'nxtf-field-text';
    }

    /**
     * Resolve class.
     *
     * @param array  $field
     * @param string $default
     * @param string $required
     * @param string $overrideKey
     * @param string $appendKey
     *
     * @return string
     */
    protected function resolveClass(
        array $field,
        string $default,
        string $required = '',
        string $overrideKey = 'class',
        string $appendKey = 'extra_class'
    ): string {
        $custom = trim((string) ($field[$overrideKey] ?? ''));
        $append = trim((string) ($field[$appendKey] ?? ''));

        $class = $custom !== '' ? $custom : trim($default . ($append !== '' ? ' ' . $append : ''));

        if ($required !== '') {
            $class = trim($class . ' ' . $required);
        }

        return $class;
    }

    /**
     * Resolve a truthy boolean-like flag from a field definition.
     *
     * @param array  $field
     * @param string $key
     * @return bool
     */
    protected function hasTruthyFlag(array $field, string $key): bool
    {
        return array_key_exists($key, $field) && $this->isTruthy($field[$key]);
    }

    /**
     * Is truthy.
     *
     * @param mixed $value
     *
     * @return bool
     */
    protected function isTruthy($value): bool
    {
        return FieldState::isTruthy($value);
    }
}
