<?php

namespace App\Services\PlanDigitizer;

/**
 * Sorts the plan's line work into the roles the digitizer needs.
 *
 * Classification leans on the AutoCAD layer names carried by the PDF's optional
 * content groups when they exist, and falls back to geometric behaviour (stroke
 * length, repetition) when the plan has no usable layer names.
 */
class SegmentClassifier
{
    /** Layer names that mark lot subdivision lines. */
    private const DIVIDER_HINTS = ['LOTE', 'LOTIF', 'PREDIO', 'DIVISION', 'DIVISIÓN', 'SUBDIVI', 'POLIGONO LOTE'];

    /** Layer names that carry commercial status hatching, mapped to a status. */
    private const STATUS_HINTS = [
        'VENDID' => 'vendido',
        'APARTAD' => 'apartado',
        'RESERVAD' => 'apartado',
        'BLOQUEAD' => 'bloqueado',
        'DISPONIBL' => 'disponible',
        'LIBRE' => 'disponible',
    ];

    /** Layer names whose line work is decoration rather than geometry. */
    private const TEXTURE_HINTS = ['HOJA', 'TALLO', 'ARBOL', 'ÁRBOL', 'VEGET', 'SYMK', 'SIMBOL', 'PLANTA', 'PASTO'];

    /** Layer names that only ever hold annotation strokes. */
    private const TEXT_HINTS = ['TEXTO', 'TEXT', 'MEDIDA', 'COTA', 'DIM', 'NOMENCLATURA', 'ROTUL'];

    public function __construct(
        private readonly float $textStrokeLimit = 4.0,
    ) {}

    /** @return list<SegmentGroup> */
    public function classify(VectorContent $content): array
    {
        /** @var array<string, SegmentGroup> $groups */
        $groups = [];
        $count = $content->segmentCount();

        for ($index = 0; $index < $count; $index++) {
            $layer = $content->layers[$content->segmentLayers[$index]] ?? '';
            $color = VectorContent::colorKey($content->colors[$content->segmentColors[$index]] ?? [0.0, 0.0, 0.0]);
            $key = $layer.'|'.$color;

            if (! isset($groups[$key])) {
                $groups[$key] = new SegmentGroup($layer, $color, []);
            }

            $groups[$key]->indexes[] = $index;
            $offset = $index * 4;
            $groups[$key]->totalLength += hypot(
                $content->coordinates[$offset + 2] - $content->coordinates[$offset],
                $content->coordinates[$offset + 3] - $content->coordinates[$offset + 1],
            );
        }

        foreach ($groups as $group) {
            $group->role = $this->roleFor($group);
            $group->status = $this->statusFor($group);
        }

        return array_values($groups);
    }

    private function roleFor(SegmentGroup $group): string
    {
        $layer = $this->fold($group->layer);

        if ($this->matches($layer, array_keys(self::STATUS_HINTS))) {
            return SegmentGroup::ROLE_STATUS;
        }

        if ($this->matches($layer, self::DIVIDER_HINTS)) {
            return SegmentGroup::ROLE_DIVIDER;
        }

        if ($this->matches($layer, self::TEXTURE_HINTS)) {
            return SegmentGroup::ROLE_TEXTURE;
        }

        if ($this->matches($layer, self::TEXT_HINTS)) {
            return SegmentGroup::ROLE_TEXT;
        }

        // Glyph and foliage strokes are short and numerous; real boundaries are long.
        if ($group->averageLength() < $this->textStrokeLimit && $group->count() > 200) {
            return SegmentGroup::ROLE_TEXTURE;
        }

        return SegmentGroup::ROLE_STRUCTURE;
    }

    private function statusFor(SegmentGroup $group): ?string
    {
        if ($group->role !== SegmentGroup::ROLE_STATUS) {
            return null;
        }

        $layer = $this->fold($group->layer);

        foreach (self::STATUS_HINTS as $hint => $status) {
            if (str_contains($layer, $hint)) {
                return $status;
            }
        }

        return null;
    }

    private function fold(string $value): string
    {
        $value = (string) mb_strtoupper($value, 'UTF-8');

        return strtr($value, ['Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ú' => 'U', 'Ñ' => 'N']);
    }

    /** @param list<string> $hints */
    private function matches(string $layer, array $hints): bool
    {
        foreach ($hints as $hint) {
            if ($hint !== '' && str_contains($layer, $this->fold($hint))) {
                return true;
            }
        }

        return false;
    }
}
