<?php

namespace App\Http\Controllers;

use App\Models\DevicePreset;
use App\Models\Project;
use App\Models\Screen;
use App\Support\AppMode;
use App\Support\LayoutMigrator;
use App\Support\LayoutResolver;
use App\Support\Templates;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class ExportController extends Controller
{
    public function store(Request $request, Project $project): JsonResponse
    {
        $this->authorizeProject($request, $project);

        // Plan gate: Free (and any plan with `can_export: false`) cannot
        // start an export. Self-hosted short-circuits inside `canExport()`.
        $user = $request->user();
        if ($user !== null && ! $user->canExport()) {
            return response()->json([
                'message' => 'Your current plan does not include export. Upgrade to a plan with export to continue.',
                'plan_key' => $user->activePlan()->key,
                'pricing_url' => route('pricing'),
            ], 402);
        }

        $projectLocales = $project->active_locales ?? ['en'];
        $projectFactors = is_array($project->active_form_factors) && $project->active_form_factors !== []
            ? array_values($project->active_form_factors)
            : [$project->form_factor];

        $data = $request->validate([
            'screenIds' => ['required', 'array', 'min:1'],
            'screenIds.*' => [
                'integer',
                Rule::exists('screens', 'id')->where('project_id', $project->id),
            ],
            'devicePresetIds' => ['sometimes', 'array'],
            'devicePresetIds.*' => [
                'integer',
                Rule::exists('device_presets', 'id')->where('is_active', true),
            ],
            'locales' => ['sometimes', 'array'],
            'locales.*' => ['string', Rule::in($projectLocales)],
            // Phase 5: factor fan-out. When omitted, defaults to the
            // project's primary factor — preserves the single-factor
            // pre-pivot behavior. Multi-factor projects pass the full
            // set to export every factor in one ZIP.
            'factors' => ['sometimes', 'array', 'min:1'],
            'factors.*' => ['string', Rule::in($projectFactors)],
        ]);

        $screens = Screen::query()
            ->where('project_id', $project->id)
            ->whereIn('id', $data['screenIds'])
            ->orderBy('position')
            ->orderBy('id')
            ->get();

        abort_if($screens->isEmpty(), 422, 'No screens matched for this project.');

        $factors = $data['factors'] ?? [$project->form_factor];
        $factors = array_values(array_unique($factors));

        // Device preset filter: callers may pass devicePresetIds to override
        // the export dimensions. When omitted, fall back to the per-factor
        // canonical canvas. The picker is scoped to the requested factors
        // — single-factor exports keep the legacy filter behavior.
        $presets = ! empty($data['devicePresetIds'] ?? null)
            ? DevicePreset::query()
                ->whereIn('id', $data['devicePresetIds'])
                ->where('is_active', true)
                ->whereIn('form_factor', $factors)
                ->get()
                ->values()
            : collect();

        // Per-factor device buckets — used by the fan-out below so each
        // factor renders against its own canvas + filtered preset list.
        $devicesByFactor = [];
        foreach ($factors as $factor) {
            $factorCanvas = Templates::defaultCanvasFor($factor);
            $factorPresets = $presets->where('form_factor', $factor);

            // Aspect-ratio gate per factor. Percent anchors + absolute font
            // sizes were tuned for the factor's canonical aspect; rendering
            // into a mismatched aspect (e.g. an iPhone SE 750×1334 preset
            // for an iPhone 6.9" 1320×2868 factor) reflows text + stretches
            // frames. Per Apple's spec, 6.x" notched vs 4.7"/5.5" classic
            // are SEPARATE submission tiers; no auto-scaling between them.
            if ($factorPresets->isNotEmpty()) {
                $factorAspect = $factorCanvas['w'] / max(1, $factorCanvas['h']);
                $tolerance = 0.01;
                $rejected = [];
                $kept = $factorPresets->filter(function (DevicePreset $p) use ($factorAspect, $tolerance, &$rejected) {
                    $aspect = $p->export_w / max(1, $p->export_h);
                    if (abs($aspect - $factorAspect) > $tolerance) {
                        $rejected[] = $p->label.' ('.$p->export_w.'×'.$p->export_h.')';
                        return false;
                    }
                    return true;
                })->values();
                if ($rejected !== []) {
                    abort(422, 'Aspect mismatch on '.$factor.': '.implode(', ', $rejected)
                        .'. Authored at '.$factorCanvas['w'].'×'.$factorCanvas['h']
                        .'; pick presets that share that aspect ratio.');
                }
                $factorPresets = $kept;
            }

            $devicesByFactor[$factor] = $factorPresets->isEmpty()
                ? [[
                    'id' => null,
                    'key' => "{$factorCanvas['w']}x{$factorCanvas['h']}",
                    'form_factor' => $factor,
                    'export_w' => $factorCanvas['w'],
                    'export_h' => $factorCanvas['h'],
                ]]
                : $factorPresets->map(fn (DevicePreset $p) => [
                    'id' => $p->id,
                    'key' => $p->key,
                    'form_factor' => $p->form_factor,
                    'export_w' => $p->export_w,
                    'export_h' => $p->export_h,
                ])->values()->all();
        }

        $localesRequested = $data['locales'] ?? [$project->default_locale ?? 'en'];
        $localesRequested = array_values(array_unique($localesRequested));

        // Direction lookup for each locale so the client renderer doesn't
        // have to walk the catalog per tile.
        $localeCatalog = \App\Support\Locales::all();
        $directionFor = function (string $code) use ($localeCatalog): string {
            foreach ($localeCatalog as $row) {
                if (($row['code'] ?? null) === $code) {
                    return $row['direction'] ?? 'ltr';
                }
            }

            return 'ltr';
        };

        // Tiles are factors × screens × locales × devices. Server-side
        // projection per factor: read stored layouts (could be v3 or v4
        // envelope), migrate v1/v2 → v3, lift to v4 envelope if needed,
        // then project to v3 for the active factor. The client renderer
        // expects v3 shape — a v4 envelope leaking into a tile would
        // crash render. ZIP path layout (computed client-side):
        //   - single-factor + single-locale + single-device → `{n}.png`
        //   - any multi-* → `{factor}/[{locale}/][{device}/]{n}.png`
        $defaultLocale = $project->default_locale ?? 'en';

        $tiles = [];
        foreach ($factors as $factor) {
            // Marketing-asset factors (Play Store feature graphic) are
            // single-asset-per-project — only the first screen carries
            // their content. Skip every non-first screen for these factors
            // so the ZIP doesn't include N blank duplicates.
            $isMarketing = LayoutResolver::isMarketingFactor($factor);
            $factorScreens = $isMarketing
                ? $screens->slice(0, 1)->values()
                : $screens->values();

            foreach ($localesRequested as $locale) {
                foreach ($devicesByFactor[$factor] as $device) {
                    foreach ($factorScreens as $i => $screen) {
                        $stored = LayoutMigrator::migrate($screen->layouts ?? []);
                        // Per-factor projection — projects v4 envelopes
                        // to their v3 slice for this factor. v3 input
                        // gets lifted to v4 first then projected back
                        // (clean round-trip; idempotent for our
                        // single-factor flow).
                        $envelope = ($stored['version'] ?? null) === LayoutMigrator::LATEST_V4_VERSION
                            ? $stored
                            : LayoutMigrator::migrateToV4($stored, $project->form_factor);
                        $layout = LayoutResolver::resolveLayoutForFactorV4($envelope, $factor);

                        // Apply locale overrides + per-locale screenshot
                        // swap for non-default locales. Default locale
                        // renders the canonical Layout unchanged.
                        if ($locale !== $defaultLocale) {
                            $layout = LayoutResolver::applyLocaleOverrides($layout, $locale, $defaultLocale);
                            $layout = LayoutResolver::applyLocaleScreenPaths($layout, $locale);
                        }

                        $tiles[] = [
                            'screenId' => $screen->id,
                            'position' => $i + 1,
                            'name' => $screen->name,
                            'layout' => $layout,
                            'factor' => $factor,
                            'device' => $device['key'],
                            'exportWidth' => $device['export_w'],
                            'exportHeight' => $device['export_h'],
                            'locale' => $locale,
                            'direction' => $directionFor($locale),
                        ];
                    }
                }
            }
        }

        // Resolve every media path the tiles reference to a renderable URL.
        // The editor page's mediaUrlMap only covers the ACTIVE factor's
        // paths, so without this map the client renderer fell back to
        // `/storage/{path}` for factors the user never opened — which 404s
        // on cloud disks and silently exported empty device bezels. Also
        // re-signs cloud URLs, covering edit sessions older than the 60-min
        // signature window.
        $mediaUrlMap = \App\Support\LayoutMediaPaths::urlMapForLayouts(
            array_column($tiles, 'layout'),
        );

        return response()->json([
            'id' => null,
            'tilesExpected' => count($tiles),
            'tiles' => $tiles,
            'projectName' => $project->name,
            'locales' => $localesRequested,
            'factors' => $factors,
            'mediaUrlMap' => $mediaUrlMap,
        ]);
    }

    private function authorizeProject(Request $request, Project $project): void
    {
        if (AppMode::isSelfHosted()) {
            abort_unless($project->tenant_id === null, 404);

            return;
        }

        abort_unless($project->tenant_id === $request->user()->tenant_id, 404);

        // Per-project visibility (Team Workspaces P3d / Δ23). ExportController
        // authorizes independently of ProjectController; a granted viewer CAN
        // export (visibility ≠ capability — export is the viewer-allowed
        // exception, not behind EnsureCanEdit), but an UNGRANTED member can't.
        // 404 (not 403) — don't leak existence.
        abort_unless($request->user()->canAccessProject($project), 404);
    }
}
