<?php

declare(strict_types=1);

namespace App\Support\References\Resolvers;

use App\Support\References\Contracts\ReferenceResolver;
use App\Support\References\ReferenceDescriptor;
use App\Support\References\ReferenceLinkTarget;
use App\Support\References\ReferenceResolutionState;
use App\Support\References\ReferenceTechnicalDetail;
use App\Support\References\ReferenceTypeLabelCatalog;
use App\Support\References\ResolvedReference;

abstract class BaseReferenceResolver implements ReferenceResolver
{
    public function __construct(
        protected readonly ReferenceTypeLabelCatalog $typeLabels,
    ) {}

    protected function technicalDetail(ReferenceDescriptor $descriptor, ?string $sourceHint = null): ReferenceTechnicalDetail
    {
        $hint = $sourceHint;

        if (! is_string($hint) || trim($hint) === '') {
            $hint = is_string($descriptor->contextValue('source_hint')) ? $descriptor->contextValue('source_hint') : null;
        }

        return ReferenceTechnicalDetail::forIdentifier(
            fullId: $descriptor->rawIdentifier,
            sourceHint: $hint,
        );
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    protected function resolved(
        ReferenceDescriptor $descriptor,
        string $primaryLabel,
        ?string $secondaryLabel = null,
        ?ReferenceLinkTarget $linkTarget = null,
        array $meta = [],
    ): ResolvedReference {
        return new ResolvedReference(
            referenceClass: $descriptor->referenceClass,
            rawIdentifier: $descriptor->rawIdentifier,
            primaryLabel: $primaryLabel,
            secondaryLabel: $secondaryLabel,
            state: ReferenceResolutionState::Resolved,
            stateLabel: null,
            linkTarget: $linkTarget,
            technicalDetail: $this->technicalDetail($descriptor),
            meta: $meta,
        );
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    protected function partiallyResolved(
        ReferenceDescriptor $descriptor,
        ?string $primaryLabel = null,
        ?string $secondaryLabel = null,
        ?ReferenceLinkTarget $linkTarget = null,
        array $meta = [],
    ): ResolvedReference {
        return new ResolvedReference(
            referenceClass: $descriptor->referenceClass,
            rawIdentifier: $descriptor->rawIdentifier,
            primaryLabel: $this->preferredLabel($descriptor, $primaryLabel),
            secondaryLabel: $secondaryLabel,
            state: ReferenceResolutionState::PartiallyResolved,
            stateLabel: null,
            linkTarget: $linkTarget,
            technicalDetail: $this->technicalDetail($descriptor),
            meta: $meta,
        );
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    protected function externalLimited(
        ReferenceDescriptor $descriptor,
        ?string $primaryLabel = null,
        ?string $secondaryLabel = null,
        array $meta = [],
    ): ResolvedReference {
        return new ResolvedReference(
            referenceClass: $descriptor->referenceClass,
            rawIdentifier: $descriptor->rawIdentifier,
            primaryLabel: $this->preferredLabel($descriptor, $primaryLabel),
            secondaryLabel: $secondaryLabel,
            state: ReferenceResolutionState::ExternalLimitedContext,
            stateLabel: null,
            linkTarget: null,
            technicalDetail: $this->technicalDetail($descriptor),
            meta: $meta,
        );
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    protected function unresolved(
        ReferenceDescriptor $descriptor,
        ?string $primaryLabel = null,
        ?string $secondaryLabel = null,
        array $meta = [],
    ): ResolvedReference {
        return new ResolvedReference(
            referenceClass: $descriptor->referenceClass,
            rawIdentifier: $descriptor->rawIdentifier,
            primaryLabel: $this->preferredLabel($descriptor, $primaryLabel),
            secondaryLabel: $secondaryLabel,
            state: ReferenceResolutionState::Unresolved,
            stateLabel: null,
            linkTarget: null,
            technicalDetail: $this->technicalDetail($descriptor),
            meta: $meta,
        );
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    protected function missing(
        ReferenceDescriptor $descriptor,
        ?string $primaryLabel = null,
        ?string $secondaryLabel = null,
        array $meta = [],
    ): ResolvedReference {
        return new ResolvedReference(
            referenceClass: $descriptor->referenceClass,
            rawIdentifier: $descriptor->rawIdentifier,
            primaryLabel: $this->preferredLabel($descriptor, $primaryLabel),
            secondaryLabel: $secondaryLabel,
            state: ReferenceResolutionState::DeletedOrMissing,
            stateLabel: null,
            linkTarget: null,
            technicalDetail: $this->technicalDetail($descriptor),
            meta: $meta,
        );
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    protected function inaccessible(
        ReferenceDescriptor $descriptor,
        ?string $primaryLabel = null,
        ?string $secondaryLabel = null,
        array $meta = [],
    ): ResolvedReference {
        return new ResolvedReference(
            referenceClass: $descriptor->referenceClass,
            rawIdentifier: $descriptor->rawIdentifier,
            primaryLabel: $this->preferredLabel($descriptor, $primaryLabel, revealFallback: false),
            secondaryLabel: $secondaryLabel,
            state: ReferenceResolutionState::Inaccessible,
            stateLabel: null,
            linkTarget: null,
            technicalDetail: $this->technicalDetail($descriptor),
            meta: $meta,
        );
    }

    protected function preferredLabel(
        ReferenceDescriptor $descriptor,
        ?string $label = null,
        bool $revealFallback = true,
    ): string {
        if (is_string($label) && trim($label) !== '') {
            return trim($label);
        }

        if ($revealFallback && is_string($descriptor->fallbackLabel) && trim($descriptor->fallbackLabel) !== '') {
            return trim($descriptor->fallbackLabel);
        }

        return $this->typeLabels->label($descriptor->referenceClass);
    }

    protected function linkedModelId(ReferenceDescriptor $descriptor): ?int
    {
        if (is_numeric($descriptor->linkedModelId) && (int) $descriptor->linkedModelId > 0) {
            return (int) $descriptor->linkedModelId;
        }

        return is_numeric($descriptor->rawIdentifier) && (int) $descriptor->rawIdentifier > 0
            ? (int) $descriptor->rawIdentifier
            : null;
    }

    protected function numericContextId(ReferenceDescriptor $descriptor, string $key): ?int
    {
        $value = $descriptor->contextValue($key);

        return is_numeric($value) && (int) $value > 0 ? (int) $value : null;
    }

    protected function contextString(ReferenceDescriptor $descriptor, string $key): ?string
    {
        $value = $descriptor->contextValue($key);

        return is_string($value) && trim($value) !== '' ? trim($value) : null;
    }
}
