<?php

namespace App\Services\Intune;

use App\Models\Policy;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class DefaultPolicyNormalizer implements PolicyTypeNormalizer
{
    private const SETTINGS_CATALOG_MAX_ROWS = 1000;

    private const SETTINGS_CATALOG_MAX_DEPTH = 8;

    /**
     * Normalize raw Intune snapshots into display-friendly blocks and warnings.
     */
    public function __construct(
        private readonly SnapshotValidator $validator,
        private readonly SettingsCatalogDefinitionResolver $definitionResolver,
        private readonly SettingsCatalogCategoryResolver $categoryResolver,
    ) {}

    public function supports(string $policyType): bool
    {
        return true;
    }

    /**
     * @return array{status: string, settings: array<int, array<string, mixed>>, settings_table?: array<string, mixed>, warnings: array<int, string>, context?: string, record_id?: string}
     */
    public function normalize(?array $snapshot, string $policyType, ?string $platform = null): array
    {
        $snapshot = $snapshot ?? [];
        $resultWarnings = [];
        $status = 'success';
        $settingsTable = null;
        $usesSettingsCatalogTable = in_array($policyType, ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'], true);
        $fallbackCategoryName = $this->extractConfigurationPolicyFallbackCategoryName($snapshot);

        $validation = $this->validator->validate($snapshot);
        $resultWarnings = array_merge($resultWarnings, $validation['warnings']);

        $odataWarning = Policy::odataTypeWarning($snapshot, $policyType, $platform);

        if ($odataWarning) {
            $resultWarnings[] = $odataWarning;
        }

        if ($snapshot === []) {
            return [
                'status' => 'warning',
                'settings' => [],
                'warnings' => array_values(array_unique(array_merge($resultWarnings, ['No snapshot available']))),
            ];
        }

        $settings = [];

        if (isset($snapshot['omaSettings']) && is_array($snapshot['omaSettings'])) {
            $settings[] = $this->normalizeOmaSettings($snapshot['omaSettings']);
        }

        if (isset($snapshot['settings']) && is_array($snapshot['settings'])) {
            if ($usesSettingsCatalogTable) {
                $normalized = $this->buildSettingsCatalogSettingsTable(
                    $snapshot['settings'],
                    fallbackCategoryName: $fallbackCategoryName
                );
                $settingsTable = $normalized['table'];
                $resultWarnings = array_merge($resultWarnings, $normalized['warnings']);
            } else {
                $settings[] = $this->normalizeSettingsCatalog($snapshot['settings']);
            }
        } elseif (isset($snapshot['settingsDelta']) && is_array($snapshot['settingsDelta'])) {
            if ($usesSettingsCatalogTable) {
                $normalized = $this->buildSettingsCatalogSettingsTable(
                    $snapshot['settingsDelta'],
                    'Settings delta',
                    $fallbackCategoryName
                );
                $settingsTable = $normalized['table'];
                $resultWarnings = array_merge($resultWarnings, $normalized['warnings']);
            } else {
                $settings[] = $this->normalizeSettingsCatalog($snapshot['settingsDelta'], 'Settings delta');
            }
        } elseif ($usesSettingsCatalogTable) {
            $resultWarnings[] = 'Settings not hydrated for this Configuration Policy.';
        }

        $settings[] = $this->normalizeStandard($snapshot);

        if (! empty($resultWarnings)) {
            $status = 'warning';
        }

        $result = [
            'status' => $status,
            'settings' => array_values(array_filter($settings)),
            'warnings' => array_values(array_unique($resultWarnings)),
        ];

        if (is_array($settingsTable) && ! empty($settingsTable['rows'] ?? [])) {
            $result['settings_table'] = $settingsTable;
        }

        return $result;
    }

    /**
     * Flatten normalized settings into key/value pairs for diffing.
     *
     * @return array<string, mixed>
     */
    public function flattenForDiff(?array $snapshot, string $policyType, ?string $platform = null): array
    {
        $normalized = $this->normalize($snapshot ?? [], $policyType, $platform);

        return $this->flattenNormalizedForDiff($normalized);
    }

    /**
     * Flatten an already normalized payload into key/value pairs for diffing.
     *
     * @param  array{settings: array<int, array<string, mixed>>, settings_table?: array<string, mixed>}  $normalized
     * @return array<string, mixed>
     */
    public function flattenNormalizedForDiff(array $normalized): array
    {
        $map = [];

        if (isset($normalized['settings_table']['rows']) && is_array($normalized['settings_table']['rows'])) {
            $title = $normalized['settings_table']['title'] ?? 'Settings';
            $prefix = is_string($title) && $title !== '' ? $title.' > ' : '';

            foreach ($normalized['settings_table']['rows'] as $row) {
                if (! is_array($row)) {
                    continue;
                }

                $key = $prefix.($row['path'] ?? $row['definition'] ?? 'entry');
                $map[$key] = $row['value'] ?? null;
            }
        }

        foreach ($normalized['settings'] ?? [] as $block) {
            if (! is_array($block)) {
                continue;
            }

            $title = $block['title'] ?? null;
            $prefix = is_string($title) && $title !== '' ? $title.' > ' : '';

            if (($block['type'] ?? null) === 'table') {
                foreach ($block['rows'] ?? [] as $row) {
                    if (! is_array($row)) {
                        continue;
                    }

                    $key = $prefix.($row['path'] ?? $row['label'] ?? 'entry');
                    $map[$key] = $row['value'] ?? null;
                }

                continue;
            }

            foreach ($block['entries'] ?? [] as $entry) {
                if (! is_array($entry)) {
                    continue;
                }

                $key = $prefix.($entry['key'] ?? 'entry');
                $map[$key] = $entry['value'] ?? null;
            }
        }

        return $map;
    }

    /**
     * @param  array<int, array<string, mixed>>  $omaSettings
     */
    private function normalizeOmaSettings(array $omaSettings): array
    {
        $rows = [];

        foreach ($omaSettings as $setting) {
            if (! is_array($setting)) {
                continue;
            }

            $rows[] = [
                'path' => $setting['omaUri'] ?? $setting['path'] ?? 'n/a',
                'value' => $setting['value'] ?? $setting['displayValue'] ?? $setting['secretReferenceValueId'] ?? null,
                'label' => $setting['displayName'] ?? null,
                'description' => $setting['description'] ?? null,
            ];
        }

        return [
            'type' => 'table',
            'title' => 'OMA-URI settings',
            'rows' => $rows,
        ];
    }

    /**
     * @param  array<int, array<string, mixed>>  $settings
     */
    private function normalizeSettingsCatalog(array $settings, string $title = 'Settings'): array
    {
        $entries = [];

        foreach ($settings as $setting) {
            if (! is_array($setting)) {
                continue;
            }

            $key = $setting['displayName'] ?? $setting['name'] ?? $setting['definitionId'] ?? 'setting';
            $value = $setting['value'] ?? $setting['settingInstance']['simpleSettingValue'] ?? $setting['simpleSettingValue'] ?? null;

            if ($value === null && isset($setting['value']['value'])) {
                $value = $setting['value']['value'];
            }

            if (is_array($value)) {
                $value = json_encode($value, JSON_PRETTY_PRINT);
            }

            $entries[] = [
                'key' => $key,
                'value' => $value,
            ];
        }

        return [
            'type' => 'keyValue',
            'title' => $title,
            'entries' => $entries,
        ];
    }

    private function extractConfigurationPolicyFallbackCategoryName(array $snapshot): ?string
    {
        $templateReference = $snapshot['templateReference'] ?? null;

        if (is_string($templateReference)) {
            $decoded = json_decode($templateReference, true);
            $templateReference = is_array($decoded) ? $decoded : null;
        }

        if (! is_array($templateReference)) {
            return null;
        }

        $displayName = $templateReference['templateDisplayName'] ?? null;

        if (is_string($displayName) && $displayName !== '') {
            return $displayName;
        }

        $family = $templateReference['templateFamily'] ?? null;

        if (is_string($family) && $family !== '') {
            return Str::headline($family);
        }

        return null;
    }

    /**
     * @param  array<int, mixed>  $settings
     * @return array{table: array<string, mixed>, warnings: array<int, string>}
     */
    private function buildSettingsCatalogSettingsTable(array $settings, string $title = 'Settings', ?string $fallbackCategoryName = null): array
    {
        $flattened = $this->flattenSettingsCatalogSettingInstances($settings, $fallbackCategoryName);

        return [
            'table' => [
                'title' => $title,
                'rows' => $flattened['rows'],
            ],
            'warnings' => $flattened['warnings'],
        ];
    }

    /**
     * @param  array<int, mixed>  $settings
     * @return array{rows: array<int, array<string, mixed>>, warnings: array<int, string>}
     */
    private function flattenSettingsCatalogSettingInstances(array $settings, ?string $fallbackCategoryName = null): array
    {
        $rows = [];
        $warnings = [];
        $rowCount = 0;
        $warnedDepthLimit = false;
        $warnedRowLimit = false;

        // Extract all definition IDs first to resolve display names in batch
        $definitionIds = $this->extractAllDefinitionIds($settings);
        $definitions = $this->definitionResolver->resolve($definitionIds);

        // Extract all category IDs and resolve them
        $categoryIds = array_filter(array_unique(array_map(
            fn ($def) => $def['categoryId'] ?? null,
            $definitions
        )));
        $categories = $this->categoryResolver->resolve($categoryIds);
        $categoryNames = [];

        foreach ($categoryIds as $categoryId) {
            $categoryName = $categories[$categoryId]['displayName'] ?? null;

            if (is_string($categoryName) && $categoryName !== '') {
                $categoryNames[] = $categoryName;
            }
        }

        $categoryNames = array_values(array_unique($categoryNames));
        $defaultCategoryName = count($categoryNames) === 1 ? $categoryNames[0] : null;

        $walk = function (array $nodes, array $pathParts, int $depth) use (
            &$walk,
            &$rows,
            &$warnings,
            &$rowCount,
            &$warnedDepthLimit,
            &$warnedRowLimit,
            $definitions,
            $categories,
            $defaultCategoryName,
            $fallbackCategoryName,
        ): void {
            if ($rowCount >= self::SETTINGS_CATALOG_MAX_ROWS) {
                if (! $warnedRowLimit) {
                    $warnings[] = sprintf('Settings truncated after %d rows.', self::SETTINGS_CATALOG_MAX_ROWS);
                    $warnedRowLimit = true;
                }

                return;
            }

            if ($depth > self::SETTINGS_CATALOG_MAX_DEPTH) {
                if (! $warnedDepthLimit) {
                    $warnings[] = sprintf('Settings nesting truncated after depth %d.', self::SETTINGS_CATALOG_MAX_DEPTH);
                    $warnedDepthLimit = true;
                }

                return;
            }

            foreach ($nodes as $node) {
                if ($rowCount >= self::SETTINGS_CATALOG_MAX_ROWS) {
                    break;
                }

                if (! is_array($node)) {
                    continue;
                }

                $instance = $this->extractSettingsCatalogSettingInstance($node);
                $definitionId = $this->extractSettingsCatalogDefinitionId($node, $instance);
                $instanceType = is_array($instance) ? ($instance['@odata.type'] ?? $node['@odata.type'] ?? null) : ($node['@odata.type'] ?? null);
                $rawInstanceType = is_string($instanceType) ? ltrim($instanceType, '#') : null;

                $currentPathParts = array_merge($pathParts, [$definitionId]);
                $path = implode(' > ', $currentPathParts);

                $value = $this->extractSettingsCatalogValue($node, $instance);

                // Get metadata from resolved definitions
                $definition = $definitions[$definitionId] ?? null;
                $displayName = $definition['displayName'] ??
                               $this->definitionResolver->prettifyDefinitionId($definitionId);
                $categoryId = $definition['categoryId'] ?? null;
                $categoryName = $categoryId ? ($categories[$categoryId]['displayName'] ?? '-') : '-';
                $description = $definition['description'] ?? null;

                if (! $categoryId && ! empty($pathParts)) {
                    foreach (array_reverse($pathParts) as $ancestorDefinitionId) {
                        if (! is_string($ancestorDefinitionId) || $ancestorDefinitionId === '') {
                            continue;
                        }

                        $ancestorDefinition = $definitions[$ancestorDefinitionId] ?? null;
                        $ancestorCategoryId = $ancestorDefinition['categoryId'] ?? null;

                        if ($ancestorCategoryId) {
                            $categoryId = $ancestorCategoryId;
                            $categoryName = $categories[$categoryId]['displayName'] ?? '-';
                            break;
                        }
                    }
                }

                if (
                    ! $categoryId
                    && $defaultCategoryName
                    && (str_contains($definitionId, '{') || str_contains($definitionId, '}'))
                ) {
                    $categoryName = $defaultCategoryName;
                }

                if (
                    $categoryName === '-'
                    && is_string($fallbackCategoryName)
                    && $fallbackCategoryName !== ''
                    && is_array($definition)
                    && ($definition['isFallback'] ?? false)
                ) {
                    $categoryName = $fallbackCategoryName;
                }

                // Convert technical type to user-friendly data type
                $dataType = $this->getUserFriendlyDataType($rawInstanceType, $value);

                $rows[] = [
                    'definition' => $displayName,
                    'definition_id' => $definitionId,
                    'category' => $categoryName,
                    'data_type' => $dataType,
                    'value' => $this->stringifySettingsCatalogValue($value),
                    'description' => $description ? Str::limit($description, 100) : '-',
                    'path' => $path,
                    'raw' => $this->pruneSettingsCatalogRaw($instance ?? $node),
                ];

                $rowCount++;

                if (! is_array($instance)) {
                    continue;
                }

                $nested = $this->extractSettingsCatalogChildren($instance);

                if (! empty($nested)) {
                    $walk($nested, $currentPathParts, $depth + 1);
                }

                if ($this->isSettingsCatalogGroupSettingCollectionInstance($instance)) {
                    $collections = $instance['groupSettingCollectionValue'] ?? [];

                    if (! is_array($collections)) {
                        continue;
                    }

                    foreach (array_values($collections) as $index => $collection) {
                        if ($rowCount >= self::SETTINGS_CATALOG_MAX_ROWS) {
                            break;
                        }

                        if (! is_array($collection)) {
                            continue;
                        }

                        $children = $collection['children'] ?? [];

                        if (! is_array($children) || empty($children)) {
                            continue;
                        }

                        $walk($children, array_merge($currentPathParts, ['['.($index + 1).']']), $depth + 1);
                    }
                }
            }
        };

        $walk($settings, [], 1);

        return [
            'rows' => array_slice($rows, 0, self::SETTINGS_CATALOG_MAX_ROWS),
            'warnings' => $warnings,
        ];
    }

    private function extractSettingsCatalogSettingInstance(array $setting): ?array
    {
        $instance = $setting['settingInstance'] ?? null;

        if (is_array($instance)) {
            return $instance;
        }

        if (isset($setting['@odata.type']) && (isset($setting['settingDefinitionId']) || isset($setting['definitionId']))) {
            return $setting;
        }

        return null;
    }

    private function extractSettingsCatalogDefinitionId(array $setting, ?array $instance): string
    {
        $candidates = [
            $setting['definitionId'] ?? null,
            $setting['settingDefinitionId'] ?? null,
            $setting['name'] ?? null,
            $setting['displayName'] ?? null,
            $instance['settingDefinitionId'] ?? null,
            $instance['definitionId'] ?? null,
        ];

        foreach ($candidates as $candidate) {
            if (is_string($candidate) && $candidate !== '') {
                return $candidate;
            }
        }

        return 'setting';
    }

    private function formatSettingsCatalogInstanceType(?string $type): ?string
    {
        if (! $type) {
            return null;
        }

        $type = Str::afterLast($type, '.');

        foreach (['deviceManagementConfiguration', 'deviceManagement'] as $prefix) {
            if (Str::startsWith($type, $prefix)) {
                $type = substr($type, strlen($prefix));

                break;
            }
        }

        return $type !== '' ? $type : null;
    }

    private function isSettingsCatalogGroupSettingCollectionInstance(array $instance): bool
    {
        $type = $instance['@odata.type'] ?? null;

        if (! is_string($type)) {
            return false;
        }

        return Str::contains($type, 'GroupSettingCollectionInstance', ignoreCase: true);
    }

    /**
     * @return array<int, mixed>
     */
    private function extractSettingsCatalogChildren(array $instance): array
    {
        foreach (['children', 'choiceSettingValue.children', 'groupSettingValue.children'] as $path) {
            $children = Arr::get($instance, $path);

            if (is_array($children) && ! empty($children)) {
                return $children;
            }
        }

        return [];
    }

    private function extractSettingsCatalogValue(array $setting, ?array $instance): mixed
    {
        if ($instance === null) {
            return $setting['value'] ?? null;
        }

        $type = $instance['@odata.type'] ?? null;
        $type = is_string($type) ? $type : '';

        if (Str::contains($type, 'ChoiceSettingCollectionInstance', ignoreCase: true)) {
            $collection = $instance['choiceSettingCollectionValue'] ?? null;

            if (! is_array($collection) || $collection === []) {
                return [];
            }

            $values = [];

            foreach ($collection as $item) {
                if (! is_array($item)) {
                    continue;
                }

                $value = $item['value'] ?? null;

                if (is_string($value) && $value !== '') {
                    $values[] = $value;
                }
            }

            return array_values(array_unique($values));
        }

        if (Str::contains($type, 'SimpleSettingInstance', ignoreCase: true)) {
            $simple = $instance['simpleSettingValue'] ?? null;

            if (is_array($simple)) {
                $simpleValue = $simple['value'] ?? $simple;

                if (is_array($simpleValue) && array_key_exists('value', $simpleValue)) {
                    return $simpleValue['value'];
                }

                return $simpleValue;
            }

            return $simple;
        }

        if (Str::contains($type, 'ChoiceSettingInstance', ignoreCase: true)) {
            $choice = $instance['choiceSettingValue'] ?? null;

            if (is_array($choice)) {
                $choiceValue = $choice['value'] ?? $choice;

                if (is_array($choiceValue) && array_key_exists('value', $choiceValue)) {
                    return $choiceValue['value'];
                }

                return $choiceValue;
            }

            return $choice;
        }

        if ($this->isSettingsCatalogGroupSettingCollectionInstance($instance) || Str::contains($type, 'GroupSettingInstance', ignoreCase: true)) {
            return '(group)';
        }

        $fallback = $instance;
        unset($fallback['children']);

        return $fallback;
    }

    private function stringifySettingsCatalogValue(mixed $value): string
    {
        if ($value === null) {
            return '-';
        }

        return $this->formatSettingsCatalogValue($value);
    }

    private function pruneSettingsCatalogRaw(mixed $raw): mixed
    {
        if (! is_array($raw)) {
            return $raw;
        }

        $pruned = $raw;
        unset($pruned['children'], $pruned['groupSettingCollectionValue']);

        return $pruned;
    }

    private function normalizeStandard(array $snapshot): array
    {
        $metadataKeys = [
            '@odata.context',
            '@odata.type',
            'id',
            'version',
            'createdDateTime',
            'lastModifiedDateTime',
            'supportsScopeTags',
            'roleScopeTagIds',
            'assignments',
            'createdBy',
            'lastModifiedBy',
            'omaSettings',
            'settings',
            'settingsDelta',
            'scheduledActionsForRule',
            'scheduledActionsForRule@odata.context',
        ];

        $filtered = Arr::except($snapshot, $metadataKeys);
        $entries = [];

        foreach ($filtered as $key => $value) {
            if (is_array($value)) {
                $value = json_encode($value, JSON_PRETTY_PRINT);
            }

            $entries[] = [
                'key' => Str::headline((string) $key),
                'value' => $value,
            ];
        }

        return [
            'type' => 'keyValue',
            'title' => 'General',
            'entries' => $entries,
        ];
    }

    /**
     * Normalize Settings Catalog policy with grouped, readable settings (T011-T014).
     *
     * @param  array<int, mixed>  $settings
     * @return array{type: string, groups: array<int, array<string, mixed>>}
     */
    public function normalizeSettingsCatalogGrouped(array $settings): array
    {
        // Extract all definition IDs
        $definitionIds = $this->extractAllDefinitionIds($settings);

        // Resolve definitions
        $definitions = $this->definitionResolver->resolve($definitionIds);

        // Flatten settings
        $flattened = $this->flattenSettingsCatalogForGrouping($settings);

        // Group by category
        $groups = $this->groupSettingsByCategory($flattened, $definitions);

        return [
            'type' => 'settings_catalog_grouped',
            'groups' => $groups,
        ];
    }

    /**
     * Extract all definition IDs from settings array recursively.
     */
    private function extractAllDefinitionIds(array $settings): array
    {
        $ids = [];

        foreach ($settings as $setting) {
            // Top-level settings have settingInstance wrapper
            if (isset($setting['settingInstance']['settingDefinitionId'])) {
                $ids[] = $setting['settingInstance']['settingDefinitionId'];
                $instance = $setting['settingInstance'];
            }
            // Nested children have settingDefinitionId directly (they ARE the instance)
            elseif (isset($setting['settingDefinitionId'])) {
                $ids[] = $setting['settingDefinitionId'];
                $instance = $setting;
            } else {
                continue;
            }

            // Handle nested children using the comprehensive children extraction method
            $children = $this->extractSettingsCatalogChildren($instance);
            if (! empty($children)) {
                $childIds = $this->extractAllDefinitionIds($children);
                $ids = array_merge($ids, $childIds);
            }

            // Also handle nested children in group collections (fallback for legacy code)
            if (isset($instance['groupSettingCollectionValue'])) {
                foreach ($instance['groupSettingCollectionValue'] as $group) {
                    if (isset($group['children']) && is_array($group['children'])) {
                        $childIds = $this->extractAllDefinitionIds($group['children']);
                        $ids = array_merge($ids, $childIds);
                    }
                }
            }
        }

        return array_unique($ids);
    }

    /**
     * Flatten settings for grouping with value formatting.
     */
    private function flattenSettingsCatalogForGrouping(array $settings): array
    {
        $rows = [];

        $walk = function (array $nodes, array $pathParts) use (&$walk, &$rows): void {
            foreach ($nodes as $node) {
                if (! is_array($node)) {
                    continue;
                }

                $instance = $this->extractSettingsCatalogSettingInstance($node);
                $definitionId = $this->extractSettingsCatalogDefinitionId($node, $instance);
                $value = $this->extractSettingsCatalogValue($node, $instance);
                $isGroupCollection = $this->isSettingsCatalogGroupSettingCollectionInstance($instance);

                // Only add to rows if NOT a group collection (those are containers)
                if (! $isGroupCollection) {
                    $rows[] = [
                        'definition_id' => $definitionId,
                        'value_raw' => $value,
                        'value_display' => $this->formatSettingsCatalogValue($value),
                        'instance_type' => is_array($instance) ? ($instance['@odata.type'] ?? null) : null,
                    ];
                }

                // Handle nested children
                if (is_array($instance)) {
                    $nested = $this->extractSettingsCatalogChildren($instance);
                    if (! empty($nested)) {
                        $walk($nested, array_merge($pathParts, [$definitionId]));
                    }

                    // Handle group collections
                    if ($this->isSettingsCatalogGroupSettingCollectionInstance($instance)) {
                        $collections = $instance['groupSettingCollectionValue'] ?? [];
                        if (is_array($collections)) {
                            foreach ($collections as $collection) {
                                if (isset($collection['children']) && is_array($collection['children'])) {
                                    $walk($collection['children'], array_merge($pathParts, [$definitionId]));
                                }
                            }
                        }
                    }
                }
            }
        };

        $walk($settings, []);

        return $rows;
    }

    /**
     * Format setting value for display (T012).
     */
    private function formatSettingsCatalogValue(mixed $value): string
    {
        if (is_bool($value)) {
            return $value ? 'Enabled' : 'Disabled';
        }

        if (is_int($value)) {
            return number_format($value);
        }

        if (is_string($value)) {
            // Remove {tenantid} placeholder
            $value = str_replace(['{tenantid}', '_tenantid_'], ['', '_'], $value);
            $value = preg_replace('/\{[^}]+\}/', '', $value);
            $value = preg_replace('/_+/', '_', $value);

            // Extract choice label from choice values (last meaningful part)
            // Example: "device_vendor_msft_...lowercaseletters_0" -> "Lowercase Letters: 0"
            if (
                str_contains($value, 'device_vendor_msft')
                || str_contains($value, 'user_vendor_msft')
                || str_contains($value, 'vendor_msft')
                || str_contains($value, '#microsoft.graph')
            ) {
                $parts = explode('_', $value);
                $lastPart = end($parts);

                // Check for boolean-like values
                if (in_array(strtolower($lastPart), ['true', 'false'])) {
                    return strtolower($lastPart) === 'true' ? 'Enabled' : 'Disabled';
                }

                $commonLastPartMapping = [
                    'in' => 'Inbound',
                    'out' => 'Outbound',
                    'allow' => 'Allow',
                    'block' => 'Block',
                    'tcp' => 'TCP',
                    'udp' => 'UDP',
                    'icmpv4' => 'ICMPv4',
                    'icmpv6' => 'ICMPv6',
                    'any' => 'Any',
                    'notconfigured' => 'Not configured',
                    'lan' => 'LAN',
                    'wireless' => 'Wireless',
                    'remoteaccess' => 'Remote access',
                    'domain' => 'Domain',
                    'private' => 'Private',
                    'public' => 'Public',
                ];

                if (is_string($lastPart) && isset($commonLastPartMapping[strtolower($lastPart)])) {
                    return $commonLastPartMapping[strtolower($lastPart)];
                }

                // If last part is just a number, take second-to-last too
                if (is_numeric($lastPart) && count($parts) > 1) {
                    $secondLast = $parts[count($parts) - 2];

                    // Map common values
                    $mapping = [
                        'lowercaseletters' => 'Lowercase Letters',
                        'uppercaseletters' => 'Uppercase Letters',
                        'specialcharacters' => 'Special Characters',
                        'digits' => 'Digits',
                    ];

                    if (isset($mapping[strtolower($secondLast)])) {
                        return $mapping[strtolower($secondLast)].': '.$lastPart;
                    }

                    if (in_array((string) $lastPart, ['0', '1'], true)) {
                        return (string) $lastPart === '1' ? 'Enabled' : 'Disabled';
                    }

                    return Str::title($secondLast).': '.$lastPart;
                }

                return Str::title($lastPart);
            }

            // Truncate long strings
            return Str::limit($value, 100);
        }

        if (is_array($value)) {
            if ($value === []) {
                return '-';
            }

            if (array_is_list($value)) {
                $parts = [];

                foreach ($value as $item) {
                    if ($item === null) {
                        continue;
                    }

                    if (! is_bool($item) && ! is_int($item) && ! is_float($item) && ! is_string($item)) {
                        $parts = [];
                        break;
                    }

                    $parts[] = $this->formatSettingsCatalogValue($item);
                }

                $parts = array_values(array_unique(array_filter($parts, static fn (string $part): bool => $part !== '' && $part !== '-')));

                if ($parts !== []) {
                    return implode(', ', $parts);
                }
            }

            return json_encode($value);
        }

        return (string) $value;
    }

    /**
     * Group settings by category (T013).
     */
    private function groupSettingsByCategory(array $rows, array $definitions): array
    {
        $grouped = [];

        foreach ($rows as $row) {
            $definitionId = $row['definition_id'];
            $definition = $definitions[$definitionId] ?? null;

            // Determine category
            $categoryId = $definition['categoryId'] ?? $this->extractCategoryFromDefinitionId($definitionId);
            $categoryTitle = $this->formatCategoryTitle($categoryId);

            if (! isset($grouped[$categoryId])) {
                $grouped[$categoryId] = [
                    'title' => $categoryTitle,
                    'description' => null,
                    'settings' => [],
                ];
            }

            $grouped[$categoryId]['settings'][] = [
                'label' => $definition['displayName'] ?? $row['definition_id'],
                'value' => $row['value_display'], // Primary value for display
                'value_display' => $row['value_display'],
                'value_raw' => $row['value_raw'],
                'help_text' => $definition['helpText'] ?? $definition['description'] ?? null,
                'definition_id' => $definitionId,
                'instance_type' => $row['instance_type'],
                'is_fallback' => $definition['isFallback'] ?? false,
            ];
        }

        // Sort groups by title
        uasort($grouped, fn ($a, $b) => strcmp($a['title'], $b['title']));

        // Sort settings within each group by label for stable ordering
        foreach ($grouped as $cid => $g) {
            if (isset($g['settings']) && is_array($g['settings'])) {
                usort($g['settings'], function ($a, $b) {
                    return strcmp(strtolower($a['label'] ?? ''), strtolower($b['label'] ?? ''));
                });

                $grouped[$cid]['settings'] = $g['settings'];
            }
        }

        return array_values($grouped);
    }

    /**
     * Extract category from definition ID (fallback grouping).
     */
    private function extractCategoryFromDefinitionId(string $definitionId): string
    {
        $parts = explode('_', $definitionId);

        // Use first 2-3 segments as category
        return implode('_', array_slice($parts, 0, min(3, count($parts))));
    }

    /**
     * Format category ID into readable title.
     */
    private function formatCategoryTitle(string $categoryId): string
    {
        // Try to prettify known patterns
        if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-/i', $categoryId)) {
            // It's a UUID - likely a category ID from Graph
            return 'Additional Settings';
        }

        // Clean up common prefixes
        $title = str_replace('device_vendor_msft_', '', $categoryId);
        $title = Str::title(str_replace('_', ' ', $title));

        // Known mappings
        $mappings = [
            'Passportforwork' => 'Windows Hello for Business',
        ];

        foreach ($mappings as $search => $replace) {
            $title = str_replace($search, $replace, $title);
        }

        return $title;
    }

    /**
     * Convert technical instance type to user-friendly data type.
     */
    private function getUserFriendlyDataType(?string $instanceType, mixed $value): string
    {
        if (! $instanceType) {
            return $this->guessDataTypeFromValue($value);
        }

        $type = strtolower($instanceType);

        if (str_contains($type, 'choice')) {
            return 'Choice';
        }

        if (str_contains($type, 'simplesetting')) {
            return $this->guessDataTypeFromValue($value);
        }

        if (str_contains($type, 'groupsetting')) {
            return 'Group';
        }

        return 'Text';
    }

    /**
     * Guess data type from value.
     */
    private function guessDataTypeFromValue(mixed $value): string
    {
        if (is_bool($value)) {
            return 'Boolean';
        }

        if (is_int($value)) {
            return 'Number';
        }

        if (is_string($value)) {
            // Check if it's a boolean-like string
            if (in_array(strtolower($value), ['true', 'false', 'enabled', 'disabled'])) {
                return 'Boolean';
            }

            // Check if numeric string
            if (is_numeric($value)) {
                return 'Number';
            }

            return 'Text';
        }

        if (is_array($value)) {
            return 'List';
        }

        return 'Text';
    }
}
