<?php

declare(strict_types=1);

namespace PrettyLinks\Pro\Reports;

use PrettyLinks\Pro\Repositories\Reports as ReportsRepo;
use PrettyLinks\Repositories\Links as LinksRepo;
use PrettyLinks\Support\SiteDate;

/**
 * Composes the Custom Reports display payload from the report definition
 * plus the analytics primitives (per-source summary + optional conversion
 * attribution).
 *
 * Extracted from `CustomReportsController::data()` so the REST JSON view
 * and the CSV export (`CustomReportCsvExporter`) build from a single
 * source of truth — the date-range defaulting, goal handling, and
 * conversion-rate math live here once.
 */
final class CustomReportData
{
    /**
     * Links repository, used to resolve the goal link's display name and
     * confirm it hasn't been hard-deleted.
     *
     * @var LinksRepo
     */
    private LinksRepo $links;

    /**
     * Construct the report-data composer.
     *
     * @param LinksRepo $links Links repository.
     */
    public function __construct(LinksRepo $links)
    {
        $this->links = $links;
    }

    /**
     * Build the composed report payload, or null when the report id does
     * not exist. `$rawFrom`/`$rawTo` accept `YYYY-MM-DD` (or a fuller
     * datetime); empty/malformed values fall back to the v3 default
     * window (first click ever → today, min 30 days back).
     *
     * @param  integer $reportId The report id to build.
     * @param  string  $rawFrom  Raw start date from the request.
     * @param  string  $rawTo    Raw end date from the request.
     * @return array{
     *     id: int,
     *     name: string,
     *     goalLinkId: int|null,
     *     goalLinkName: string,
     *     from: string,
     *     to: string,
     *     sources: array<int, array<string, mixed>>
     * }|null
     */
    public function build(int $reportId, string $rawFrom, string $rawTo): ?array
    {
        $report = (new ReportsRepo())->find($reportId);
        if ($report === null) {
            return null;
        }

        $analytics = new CustomReports();

        $from = self::normalizeDate($rawFrom);
        $to   = self::normalizeDate($rawTo);
        if ($from === '' || $to === '') {
            // Site-local calendar day (not UTC) so default ranges match the
            // main reports / date picker in non-UTC timezones.
            $today   = SiteDate::todayYmd();
            $floor   = SiteDate::daysAgoYmd(30);
            $first   = $analytics->firstClickDate($report['link_ids']);
            $default = ($first !== null && $first < $floor) ? $first : $floor;
            if ($from === '') {
                $from = $default;
            }
            if ($to === '') {
                $to = $today;
            }
        }

        list($from, $to) = SiteDate::orderYmd($from, $to);

        $summary = $analytics->summaryBySource($report['link_ids'], $from, $to);

        $goalLinkId   = $report['goal_link_id'];
        $goalLinkName = '';
        $conversions  = [];
        if ($goalLinkId !== null && $goalLinkId > 0) {
            $goal = $this->links->find($goalLinkId);
            if ($goal !== null) {
                $goalLinkName = (string) ($goal['name'] ?? '');
                $conversions  = (new Conversions())->bySource(
                    $report['link_ids'],
                    $goalLinkId,
                    $from,
                    $to
                );
            } else {
                // Goal link has been hard-deleted — treat as unset so the
                // UI / CSV don't render a dead conversions column.
                $goalLinkId = null;
            }
        }

        $sources = [];
        foreach ($summary as $row) {
            $clicks  = (int) $row['clicks'];
            $uniques = (int) $row['uniques'];
            $entry   = [
                'linkId'  => (int) $row['link_id'],
                'name'    => (string) $row['name'],
                'slug'    => (string) ($row['slug'] ?? ''),
                'url'     => (string) ($row['url'] ?? ''),
                'clicks'  => $clicks,
                'uniques' => $uniques,
            ];
            if ($goalLinkId !== null) {
                $convs = isset($conversions[$row['link_id']]) ? (int) $conversions[$row['link_id']] : 0;
                // Custom-reports conv rate uses ALL CLICKS as denominator.
                // This is distinct from split-test (uniques-based) — a
                // deliberate v3 carry-forward.
                $convRate             = $clicks > 0 ? round(($convs / $clicks) * 100, 2) : 0.0;
                $entry['conversions'] = $convs;
                $entry['convRate']    = $convRate;
            }
            $sources[] = $entry;
        }

        return [
            'id'           => $report['id'],
            'name'         => $report['name'],
            'goalLinkId'   => $goalLinkId,
            'goalLinkName' => $goalLinkName,
            'from'         => $from,
            'to'           => $to,
            'sources'      => $sources,
        ];
    }

    /**
     * Accepts `YYYY-MM-DD` or a full datetime; returns a real calendar
     * YYYY-MM-DD or '' when missing/invalid (caller substitutes a default).
     *
     * @param string $value Raw date or datetime string.
     */
    private static function normalizeDate(string $value): string
    {
        if (preg_match('/^(\d{4}-\d{2}-\d{2})/', trim($value), $m) !== 1) {
            return '';
        }

        return SiteDate::normalizeYmd($m[1]);
    }
}
