<?php

namespace App\Http\Controllers\Api\V1\Website;

use App\Domain\Website\Services\SiteContentService;
use App\Http\Controllers\Controller;
use App\Support\ApiResponse;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * Shared CRUD for the simple CMS collections (§150).
 *
 * Every subclass names its model, its validation rules and how a row is shaped
 * for the admin UI. Nothing here is generic over the *rules* — each collection
 * still states exactly what it accepts.
 */
abstract class CmsResourceController extends Controller
{
    public function __construct(protected readonly SiteContentService $site) {}

    /** @return class-string<Model> */
    abstract protected function model(): string;

    /** @return array<string, mixed> */
    abstract protected function rules(bool $creating): array;

    /** @return array<string, mixed> */
    abstract protected function present(Model $record): array;

    /** Columns the list is ordered by. */
    protected function ordering(): array
    {
        return ['sort_order', 'id'];
    }

    /** Hook for subclasses that derive values, such as a slug. */
    protected function prepare(array $data, ?Model $record = null, ?int $companyId = null): array
    {
        return $data;
    }

    public function index(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('cms.view'), 403);

        $records = $this->model()::query()
            ->where('company_id', $request->user()->company_id)
            ->orderBy($this->ordering()[0])
            ->orderBy($this->ordering()[1] ?? 'id')
            ->get();

        return ApiResponse::success($records->map(fn (Model $record) => $this->present($record)));
    }

    public function store(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('cms.manage'), 403);

        $data = $this->prepare(
            $request->validate($this->rules(true)),
            null,
            $request->user()->company_id,
        );
        $data['company_id'] = $request->user()->company_id;

        $record = $this->model()::create($data);
        $this->site->flush();

        return ApiResponse::success($this->present($record), 'Created.', status: 201);
    }

    public function update(Request $request, string $uuid): JsonResponse
    {
        abort_unless($request->user()->can('cms.manage'), 403);

        $record = $this->find($request, $uuid);
        $data = $this->prepare(
            $request->validate($this->rules(false)),
            $record,
            $request->user()->company_id,
        );

        $record->update($data);
        $this->site->flush();

        return ApiResponse::success($this->present($record->fresh()), 'Saved.');
    }

    public function destroy(Request $request, string $uuid): JsonResponse
    {
        abort_unless($request->user()->can('cms.manage'), 403);

        $this->find($request, $uuid)->delete();
        $this->site->flush();

        return ApiResponse::success(null, 'Deleted.');
    }

    /**
     * A slug unique within the company. Collisions are normal — two countries
     * can share a name across companies, and an editor may retype an existing
     * one — so a suffix is appended rather than letting the index throw.
     */
    protected function uniqueSlug(string $value, int $companyId): string
    {
        $base = Str::slug($value) ?: 'item';
        $slug = $base;
        $suffix = 2;

        while ($this->model()::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->where('slug', $slug)
            ->exists()) {
            $slug = "{$base}-{$suffix}";
            $suffix++;
        }

        return $slug;
    }

    protected function find(Request $request, string $uuid): Model
    {
        $record = $this->model()::query()
            ->where('uuid', $uuid)
            // Scoped to the caller's company, so a guessed id reaches nothing.
            ->where('company_id', $request->user()->company_id)
            ->first();

        abort_if($record === null, 404);

        return $record;
    }
}
