<?php
declare(strict_types=1);

namespace Infolab\Controllers;

use Infolab\Database;
use Infolab\Request;
use Infolab\Response;
use Infolab\Slugger;
use Infolab\Validator;

/**
 * CRUD des catégories thématiques (Data, Démocratie, Habitat, …).
 * Les catégories sont seedées par migration ; les écritures restent disponibles
 * pour ajustements ponctuels (token requis).
 */
final class CategoriesController
{
    private const SELECT = 'SELECT id, slug, name, description, color_hex, icon, dimension, sort_order, created_at, updated_at FROM categories';

    public function index(Request $req, array $params): void
    {
        $rows = Database::fetchAll(self::SELECT . ' ORDER BY sort_order ASC, id ASC');
        Response::json(['data' => array_map([self::class, 'present'], $rows)]);
    }

    public function show(Request $req, array $params): void
    {
        $cat = self::findOr404($params['slug']);
        $cat['projects_count'] = (int) Database::fetchValue(
            'SELECT COUNT(*) FROM projects WHERE category_id = :id AND deleted_at IS NULL',
            [':id' => (int) $cat['id']],
        );
        Response::json(self::present($cat) + ['projects_count' => $cat['projects_count']]);
    }

    public function store(Request $req, array $params): void
    {
        $body = $req->json();
        $v = new Validator($body);
        $v->required('name')->string('name', 128);
        $v->string('slug', 64);
        $v->string('description', 5000);
        $v->colorHex('color_hex');
        $v->string('icon', 32);
        $v->in('dimension', ['social', 'ecologique', 'transversal']);
        $v->integer('sort_order', 0);
        $v->check();

        $name = (string) $body['name'];
        $base = (isset($body['slug']) && $body['slug'] !== '')
            ? Slugger::slugify((string) $body['slug'])
            : Slugger::slugify($name);
        $slug = Slugger::unique($base, 'categories');

        Database::insert('categories', [
            'slug'        => $slug,
            'name'        => $name,
            'description' => $body['description'] ?? null,
            'color_hex'   => $body['color_hex'] ?? '#888888',
            'icon'        => $body['icon'] ?? null,
            'dimension'   => $body['dimension'] ?? 'transversal',
            'sort_order'  => isset($body['sort_order']) ? (int) $body['sort_order'] : 0,
        ]);
        Response::json(self::present(self::findOr404($slug)), 201);
    }

    public function update(Request $req, array $params): void
    {
        $cat = self::findOr404($params['slug']);
        $body = $req->json();
        $v = new Validator($body);
        $v->string('name', 128);
        $v->string('slug', 64);
        $v->string('description', 5000);
        $v->colorHex('color_hex');
        $v->string('icon', 32);
        $v->in('dimension', ['social', 'ecologique', 'transversal']);
        $v->integer('sort_order', 0);
        $v->check();

        $update = [];
        foreach (['name', 'description', 'color_hex', 'icon', 'dimension', 'sort_order'] as $f) {
            if (array_key_exists($f, $body)) {
                $update[$f] = $body[$f];
            }
        }
        if (isset($update['sort_order'])) {
            $update['sort_order'] = (int) $update['sort_order'];
        }
        if (isset($body['slug']) && $body['slug'] !== '') {
            $update['slug'] = Slugger::unique(
                Slugger::slugify((string) $body['slug']),
                'categories',
                'slug',
                (int) $cat['id'],
            );
        }
        if ($update !== []) {
            Database::update('categories', $update, 'id = :id', [':id' => (int) $cat['id']]);
        }
        Response::json(self::present(self::findOr404($update['slug'] ?? $cat['slug'])));
    }

    public function destroy(Request $req, array $params): void
    {
        $cat = self::findOr404($params['slug']);
        // FK ON DELETE RESTRICT côté projects -> on vérifie nous-mêmes pour un
        // message propre. Compte aussi les projets soft-deletés (la FK ne fait
        // pas la différence et bloquerait sinon en cascade).
        $count = (int) Database::fetchValue(
            'SELECT COUNT(*) FROM projects WHERE category_id = :id',
            [':id' => (int) $cat['id']],
        );
        if ($count > 0) {
            throw new \ApiException(409, [
                'error'   => 'conflict',
                'message' => sprintf('Cette catégorie est utilisée par %d projet(s).', $count),
            ]);
        }
        Database::delete('categories', 'id = :id', [':id' => (int) $cat['id']]);
        Response::noContent();
    }

    private static function findOr404(string $slug): array
    {
        $row = Database::fetchOne(self::SELECT . ' WHERE slug = :slug', [':slug' => $slug]);
        if ($row === null) {
            throw new \ApiException(404, ['error' => 'not_found']);
        }
        return $row;
    }

    private static function present(array $row): array
    {
        return [
            'id'          => (int) $row['id'],
            'slug'        => $row['slug'],
            'name'        => $row['name'],
            'description' => $row['description'],
            'color_hex'   => $row['color_hex'],
            'icon'        => $row['icon'],
            'dimension'   => $row['dimension'],
            'sort_order'  => (int) $row['sort_order'],
            'created_at'  => $row['created_at'],
            'updated_at'  => $row['updated_at'],
        ];
    }
}
