<?php

declare(strict_types=1);

namespace App\Services;

use RuntimeException;

/**
 * Professional 16:9 executive PowerPoint generated from a controlled OOXML
 * template. Charts are native vector shapes whose dimensions are populated
 * from the frozen report data.
 */
final class DiarPowerPointExportService
{
    /** @param array<string,mixed> $report */
    public function create(string $path, array $report, string $username): void
    {
        $template = base_path('resources/templates/diar/DIAR_Executive_Report_Template.pptx');
        if (!is_file($template)) {
            throw new RuntimeException('DIAR_POWERPOINT_TEMPLATE_MISSING');
        }
        $entries = DiarOoxmlPackage::read($template);
        $replacements = $this->replacements($report, $username);
        foreach ($entries as $name => $content) {
            if (preg_match('#^(ppt/(slides|slideMasters)/.+\.xml|docProps/(core|app)\.xml)$#', $name) === 1) {
                $entries[$name] = strtr($content, $replacements);
            }
        }
        foreach ($entries as $name => $content) {
            if (strpos($content, '[[') !== false && preg_match('#^ppt/(slides|slideMasters)/.+\.xml$#', $name) === 1) {
                throw new RuntimeException('DIAR_POWERPOINT_UNRESOLVED_TOKEN|' . $name);
            }
        }
        DiarOoxmlPackage::write($path, $entries);
    }

    /** @param array<string,mixed> $report @return array<string,string> */
    private function replacements(array $report, string $username): array
    {
        $summary = (array)($report['summary'] ?? []);
        $cases = max(1, (int)($summary['case_count'] ?? $summary['detected_count'] ?? 0));
        $critical = (int)($summary['critical_count'] ?? 0);
        $high = (int)($summary['high_count'] ?? 0);
        $medium = (int)($summary['medium_count'] ?? 0);
        $cost = (float)($summary['inventory_cost_abs'] ?? 0);
        $scope = $this->scopeLabel($report);
        $snapshots = (array)($report['snapshot_library'] ?? []);
        $sourceIdentities = [];
        $snapshotRefs = [];
        $siteMap = [];
        foreach ($snapshots as $row) {
            if (!is_array($row)) continue;
            $site = strtoupper((string)($row['site_code'] ?? ''));
            $siteMap[$site] = $row;
            $sourceIdentities[] = $site . '/' . (string)($row['source_db'] ?? '');
            $snapshotRefs[] = $site . '#' . (string)($row['snapshot_id'] ?? '') . 'v' . (string)($row['version_no'] ?? '');
        }
        $replacements = [
            '[[SCOPE]]' => $this->xml($scope),
            '[[MONTH]]' => $this->xml((string)($report['selected_month'] ?? '')),
            '[[GENERATED_AT]]' => $this->xml(date('Y-m-d H:i')),
            '[[GENERATED_BY]]' => $this->xml($username),
            '[[SOURCE_IDENTITY]]' => $this->xml(implode(' · ', $sourceIdentities)),
            '[[SNAPSHOT_REFS]]' => $this->xml(implode(' · ', $snapshotRefs)),
            '[[WORKBOOK_SHA]]' => substr(DiarReportCatalog::APPROVED_WORKBOOK_SHA256, 0, 20) . '…',
            '[[FILE_SHA]]' => 'Recorded in companion export manifest',
            '[[CASES]]' => number_format($cases),
            '[[CRITICAL]]' => number_format($critical),
            '[[HIGH]]' => number_format($high),
            '[[MEDIUM]]' => number_format($medium),
            '[[COST]]' => number_format($cost, 2),
            '[[GROUPS]]' => number_format((int)($summary['movement_groups_reviewed'] ?? 0)),
            '[[CASE_SHARE]]' => number_format((float)($summary['case_share'] ?? 0) * 100, 2) . '%',
            '[[TRANSACTIONS]]' => number_format((int)($summary['candidate_txn_count'] ?? 0)),
            '[[LINES]]' => number_format((int)($summary['candidate_line_count'] ?? 0)),
            '[[STORES]]' => number_format((int)($summary['store_count'] ?? 0)),
            '[[ITEMS]]' => number_format((int)($summary['item_count'] ?? 0)),
            '[[USERS]]' => number_format((int)($summary['user_count'] ?? 0)),
            '[[DAYS]]' => number_format((int)($summary['completed_days'] ?? 0)),
            '[[CRITICAL_PCT]]' => number_format(($critical / $cases) * 100, 1) . '%',
            '[[HIGH_PCT]]' => number_format(($high / $cases) * 100, 1) . '%',
            '[[MEDIUM_PCT]]' => number_format(($medium / $cases) * 100, 1) . '%',
            '[[CRITICAL_COST]]' => number_format((float)($report['statistical_basis']['priority']['CRITICAL']['cost'] ?? 0), 2),
        ];

        $insights = array_values((array)($report['insights'] ?? []));
        for ($i = 1; $i <= 5; $i++) {
            $replacements['[[INSIGHT_' . $i . ']]'] = $this->xml((string)($insights[$i - 1] ?? ''));
        }
        $topStore = (array)($report['top_stores'][0] ?? []);
        $replacements['[[DECISION_FOCUS]]'] = $this->xml(
            $topStore !== []
                ? 'Begin with ' . (string)($topStore['store'] ?? 'the leading store') . ', then work through critical and higher-cost cases.'
                : 'Begin with critical and higher-cost cases.'
        );

        $replacements['[[SEV_CRITICAL_CX]]'] = (string)$this->barWidth($critical, max($critical, $high, $medium), 7680960);
        $replacements['[[SEV_HIGH_CX]]'] = (string)$this->barWidth($high, max($critical, $high, $medium), 7680960);
        $replacements['[[SEV_MEDIUM_CX]]'] = (string)$this->barWidth($medium, max($critical, $high, $medium), 7680960);

        foreach (['MNH', 'NJCH'] as $site) {
            $row = (array)($siteMap[$site] ?? []);
            $replacements['[[' . $site . '_CASES]]'] = number_format((int)($row['cases'] ?? 0));
            $replacements['[[' . $site . '_COST]]'] = number_format((float)($row['associated_cost'] ?? 0), 2);
            $replacements['[[' . $site . '_SNAPSHOT]]'] = (string)($row['snapshot_id'] ?? '—');
            $replacements['[[' . $site . '_VERSION]]'] = (string)($row['version_no'] ?? '—');
            $replacements['[[' . $site . '_STATUS]]'] = $this->xml((string)($row['status'] ?? '—'));
        }
        $maxSite = max(1, (int)($siteMap['MNH']['cases'] ?? 0), (int)($siteMap['NJCH']['cases'] ?? 0));
        $replacements['[[HOSP_MNH_CX]]'] = (string)$this->barWidth((int)($siteMap['MNH']['cases'] ?? 0), $maxSite, 7680960);
        $replacements['[[HOSP_NJCH_CX]]'] = (string)$this->barWidth((int)($siteMap['NJCH']['cases'] ?? 0), $maxSite, 7680960);

        $this->topBars($replacements, 'STORE', (array)($report['top_stores'] ?? []), 'store', 'cases', 'cost_value', 7680960);
        $this->topBars($replacements, 'ITEM', (array)($report['top_items'] ?? []), 'item', 'cases', null, 2148840);
        $this->topBars($replacements, 'MOVE', (array)($report['top_movements'] ?? []), 'movement_type', 'cases', null, 2148840);

        $daily = $this->sampleDaily((array)($report['daily_distribution'] ?? []), 10);
        $maxDaily = 1;
        foreach ($daily as $row) $maxDaily = max($maxDaily, (int)($row['cases'] ?? 0));
        for ($i = 1; $i <= 10; $i++) {
            $row = (array)($daily[$i - 1] ?? []);
            $value = (int)($row['cases'] ?? 0);
            $cy = $this->barHeight($value, $maxDaily, 1097280);
            $replacements['[[DAY_' . $i . '_CY]]'] = (string)$cy;
            $replacements['[[DAY_' . $i . '_Y]]'] = (string)(5120640 - $cy);
            $replacements['[[DAY_' . $i . '_VALUE]]'] = $value > 0 ? number_format($value) : '';
            $date = (string)($row['date'] ?? '');
            $replacements['[[DAY_' . $i . '_LABEL]]'] = $date !== '' ? substr($date, 8, 2) : '';
        }
        $peak = $this->peakDay((array)($report['daily_distribution'] ?? []));
        $replacements['[[PEAK_DAY]]'] = $this->xml((string)($peak['date'] ?? '—'));
        $replacements['[[PEAK_CASES]]'] = number_format((int)($peak['cases'] ?? 0));
        $replacements['[[PEAK_COST]]'] = number_format((float)($peak['cost_value'] ?? 0), 2);

        $criticalRows = array_values((array)($report['critical_cases'] ?? []));
        for ($i = 1; $i <= 8; $i++) {
            $row = (array)($criticalRows[$i - 1] ?? []);
            $replacements['[[CASE_' . $i . '_RANK]]'] = (string)($row['rank'] ?? '');
            $replacements['[[CASE_' . $i . '_ID]]'] = $this->xml((string)($row['case_id'] ?? ''));
            $replacements['[[CASE_' . $i . '_STORE]]'] = $this->xml($this->short((string)($row['store_display'] ?? ''), 32));
            $replacements['[[CASE_' . $i . '_ITEM]]'] = $this->xml($this->short((string)($row['item_display'] ?? ''), 34));
            $replacements['[[CASE_' . $i . '_MOVE]]'] = $this->xml($this->short((string)($row['movement_display'] ?? ''), 22));
            $replacements['[[CASE_' . $i . '_SCORE]]'] = isset($row['score']) ? number_format((float)$row['score'], 1) : '';
            $replacements['[[CASE_' . $i . '_VALUE]]'] = isset($row['case_value']) ? number_format((float)$row['case_value'], 0) : '';
        }

        $actions = array_values((array)($report['recommendations'] ?? []));
        for ($i = 1; $i <= 5; $i++) {
            $replacements['[[ACTION_' . $i . ']]'] = $this->xml((string)($actions[$i - 1] ?? ''));
        }
        return $replacements;
    }

    /** @param array<string,string> $replacements @param array<int,array<string,mixed>> $rows */
    private function topBars(array &$replacements, string $prefix, array $rows, string $nameKey, string $valueKey, $costKey, int $maxWidth): void
    {
        $max = 1;
        foreach ($rows as $row) $max = max($max, (int)($row[$valueKey] ?? 0));
        for ($i = 1; $i <= 5; $i++) {
            $row = (array)($rows[$i - 1] ?? []);
            $value = (int)($row[$valueKey] ?? 0);
            $replacements['[[' . $prefix . '_' . $i . '_NAME]]'] = $this->xml($this->short((string)($row[$nameKey] ?? ''), 34));
            $replacements['[[' . $prefix . '_' . $i . '_CASES]]'] = $value > 0 ? number_format($value) : '';
            $replacements['[[' . $prefix . '_' . $i . '_COST]]'] = $costKey !== null && isset($row[$costKey]) ? number_format((float)$row[$costKey], 0) : '';
            $replacements['[[' . $prefix . '_' . $i . '_CX]]'] = (string)$this->barWidth($value, $max, $maxWidth);
        }
    }

    private function barWidth(int $value, int $max, int $maxWidth): int
    {
        if ($value <= 0 || $max <= 0) return 1000;
        return max(1000, (int)round(($value / $max) * $maxWidth));
    }

    private function barHeight(int $value, int $max, int $maxHeight): int
    {
        if ($value <= 0 || $max <= 0) return 1000;
        return max(1000, (int)round(($value / $max) * $maxHeight));
    }

    /** @param array<int,array<string,mixed>> $rows @return array<int,array<string,mixed>> */
    private function sampleDaily(array $rows, int $count): array
    {
        $total = count($rows);
        if ($total <= $count) return $rows;
        $sample = [];
        for ($i = 0; $i < $count; $i++) {
            $index = (int)round(($i / max(1, $count - 1)) * ($total - 1));
            $sample[] = $rows[$index];
        }
        return $sample;
    }

    /** @param array<int,array<string,mixed>> $rows @return array<string,mixed> */
    private function peakDay(array $rows): array
    {
        usort($rows, static function (array $a, array $b): int {
            return ((int)($b['cases'] ?? 0) <=> (int)($a['cases'] ?? 0)) ?: ((float)($b['cost_value'] ?? 0) <=> (float)($a['cost_value'] ?? 0));
        });
        return (array)($rows[0] ?? []);
    }

    /** @param array<string,mixed> $report */
    private function scopeLabel(array $report): string
    {
        $scope = strtoupper((string)($report['scope_code'] ?? 'ALL'));
        if ($scope === 'MNH') return 'MNH Hospital';
        if ($scope === 'NJCH') return 'NJCH Hospital';
        return 'HMH Combined — MNH and NJCH';
    }

    private function short(string $value, int $length): string
    {
        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            return mb_strlen($value, 'UTF-8') > $length ? mb_substr($value, 0, $length - 1, 'UTF-8') . '…' : $value;
        }
        $characters = preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY);
        if (is_array($characters)) {
            return count($characters) > $length ? implode('', array_slice($characters, 0, $length - 1)) . '…' : $value;
        }
        return strlen($value) > $length ? substr($value, 0, $length - 1) . '…' : $value;
    }

    private function xml(string $value): string
    {
        return htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8');
    }
}
