<?php
// +-------------------------------------------------+
// © 2002-2004 PMB Services / www.sigb.net pmb@sigb.net et contributeurs (voir www.sigb.net)
// +-------------------------------------------------+
// $Id: ProfileExport.php,v 1.3.2.2 2025/06/27 14:20:52 rtigero Exp $

namespace Pmb\ImportExport\Models\Profiles;

use Pmb\ImportExport\Models\Ontology\OntologyPMB;

class ProfileExport extends Profile
{
    protected const PROFILE_PURPOSE = "export";
    protected const MAX_DEPTH = 5;

    public $profileType = "all_entities";
    protected OntologyPMB $onto;
    private $exportedEntities = array();
    private $unexportedEntities = array();
    private $defaultDepth = self::MAX_DEPTH;

    public function applyProfile()
    {
        $this->setupProfile();
        foreach ($this->exportedEntities as $entity) {
            $this->applyProfileEntity($entity);
        }
        $this->removeUnexportedEntities();
    }

    protected function setupProfile()
    {
        $this->onto = OntologyPMB::getInstance();
        foreach ($this->entities as $entity) {
            if ($this->isUnexportedEntity($entity)) {
                $this->unexportedEntities[] = $entity;
            } else {
                $this->exportedEntities[] = $entity;
            }
        }

        $this->fetchDefaultDepth();
    }

    protected function fetchDefaultDepth(): void
    {
        if ($this->profileType != "unlimited_depth" && strpos($this->profileType, "limited_depth_") !== false) {
            $this->defaultDepth = intval(str_replace("limited_depth_", "", $this->profileType));
        }
    }

    /**
     * Supprime les entités non exportées
     * @return void
     */
    protected function removeUnexportedEntities(): void
    {
        foreach ($this->unexportedEntities as $entity) {
            $type = $entity->entityType;
            //2 delete un pour supprimer l'entité en tant qu'objet et l'autre en tant que sujet
            //TODO supprimer les propriétés intermédiaires
            $query = "DELETE FROM <" . $this->store->getGraphURI() . "> {
                ?object ?predicate ?subject .
            } WHERE {
                ?subject rdf:type <" . $type . "> .
                ?object ?predicate ?subject .
            }";
            $this->store->query($query);
            $query = "DELETE FROM <" . $this->store->getGraphURI() . "> {
                ?subject ?predicate ?object .
            } WHERE {
                ?subject rdf:type <" . $type . "> .
                ?subject ?predicate ?object .
            }";

            $this->store->query($query);
        }
    }

    private function isUnexportedEntity(ProfileEntity $entity): bool
    {
        if (! isset($entity->entitySettings) || ! isset($entity->entitySettings->subEntities)) {
            return true;
        }

        foreach ($entity->entitySettings->subEntities as $subEntity) {
            foreach ($subEntity->levels as $level) {
                if ($level->activated) {
                    return false;
                }
            }
        }

        return true;
    }

    private function applyProfileEntity(ProfileEntity $entity): void
    {
        //La première entité est l'entité par défaut
        $defaultSubEntity = array_shift($entity->entitySettings->subEntities);
        $depth = is_numeric($defaultSubEntity->depth) ? $defaultSubEntity->depth : $this->defaultDepth;
        $entities = $this->getSubEntities($entity->entityType, $defaultSubEntity->fields);
        if (empty($entities)) {
            return;
        }

        foreach ($entity->entitySettings->subEntities as $subEntity) {
            $subEntityEntities = $this->getSubEntities($entity->entityType, $subEntity->fields);

            //On n'a rien trouvé pour la sous entité, on passe à la suite
            if (empty($subEntityEntities)) {
                continue;
            }

            //On applique l'éventuelle surcharge de la profondeur limite
            $subDepth = is_numeric($subEntity->depth) ? $subEntity->depth : $depth;
            $this->removeOverdepthEntities($subEntityEntities, $subDepth);

            //On retire de la liste les entités que l'on va traiter dans cette sous entité
            $entities = array_diff($entities, $subEntityEntities);
            //LEVELS ABOOOOOOOOOOVE
            $this->applyLevels($subEntityEntities, $subEntity->levels);
        }
        //Plus qu'a traiter les entités restantes
        $this->removeOverdepthEntities($entities, $depth);
        $this->applyLevels($entities, $defaultSubEntity->levels);
    }

    private function applyLevels($entities, $levels)
    {
        //Le premier niveau du tableau est le niveau par défaut
        $defaultLevel = array_shift($levels);

        foreach ($levels as $level => $levelInfo) {
            if ($levelInfo->customLevel == false) {
                continue;
            }
            $levelEntities = $this->getLevelEntities($entities, $level);
            $entities = array_diff($entities, $levelEntities);
            $this->applyLevel($entities, $level, $levelInfo);
        }
    }

    private function getSubEntities(string $entityType, array $fields): array
    {
        $query = "SELECT DISTINCT ?uri WHERE {
            ?uri rdf:type <" . $entityType . "> .
            ?uri ?p ?o .";
        $filterValue = "";
        foreach ($fields as $field) {
            // if ($field->fieldOperator && $field->fieldOperator == "OR") {
            //     $optionalValue .= " UNION { ?uri <" . $field->fieldCode . "> " . ($this->getFieldValue($field) ?? "''") . " . }";
            // } else {
            $filterValue .= " ?uri <" . $field->fieldCode . "> " . ($this->getFieldValue($field) ?? "''") . " . ";
            // }
        }
        if ($filterValue != "") {
            $query .= $filterValue;
        }
        $query .= " }";
        $result = $this->store->query($query);
        if (is_array($result)) {
            return array_column($result, "uri");
        }
        return array();
    }

    private function getFieldValue($field)
    {
        global $opac_url_base;

        if ($this->onto->propertyExists($field->fieldCode)) {
            $property = $this->onto->getPropertyByURI($field->fieldCode);
            foreach ($property->ranges as $range) {
                if ($this->onto->entityExists($range->uri)) {
                    if (is_numeric($field->fieldDefaultValue)) {
                        return "<" . $opac_url_base . $property->flag . "#" . intval($field->fieldDefaultValue) . ">";
                    } else {
                        return "<" . $field->fieldDefaultValue . ">";
                    }
                }
            }
        }
        return "'" . $field->fieldDefaultValue . "'";
    }

    private function applyLevel($entities, $level, $levelInfo) {}

    private function getLevelEntities(array $entities, int $level): array
    {
        $query = "SELECT DISTINCT ?uri WHERE {
            ?uri pmb:depth '" . $level . "' .";
        $query .= " ?uri ?predicate ?object .
            FILTER(?uri = <" . implode("> || ?uri = <", $entities) . ">)
        }";
        $result = $this->store->query($query);
        if (is_array($result)) {
            return array_column($result, "uri");
        }
        return array();
    }

    private function removeOverdepthEntities(array $entities, int $depth)
    {
        if (empty($entities)) {
            return;
        }

        $query = "DELETE FROM <" . $this->store->getGraphURI() . "> {
            ?uri ?predicate ?object .
            ?reverseObject ?reversePredicate ?uri .
            } WHERE {
                ?uri pmb:depth ?depth .
                ?uri ?predicate ?object .
                ?reverseObject ?reversePredicate ?uri .";
        $query .= " ?uri ?predicate ?object .
            FILTER(?uri = <" . implode("> || ?uri = <", $entities) . ">)
            FILTER(?depth > " . $depth . ")
        }";
        $this->store->query($query);
    }

    /**
     * Retourne un tableau représentant les niveaux de profondeur par entité
     * En clé en le type d'entité, en valeur le niveau max de profondeur requis
     * Si false, l'entité n'est pas à exporter
     *
     * @return array
     */
    public function getDepthByEntities(): array
    {
        $depthByEntities = array();
        $this->fetchDefaultDepth();

        foreach ($this->entities as $entity) {
            $type = explode("#", $entity->entityType);
            $type = strtolower($type[1]);
            if ($this->isUnexportedEntity($entity)) {
                $depthByEntities[$type] = false;
                //Cas particulier des auteurs, si on ne prend pas les auteurs
                //Alors on ne prend pas les responsabilités non plus
                //A voir plus tard si c'est possible de faire mieux
                if ($type == "author") {
                    $depthByEntities["responsability"] = false;
                    $depthByEntities["responsability_tu"] = false;
                }
                continue;
            }
            $depthByEntities[$type] = $this->defaultDepth;
            $subEntities = $entity->entitySettings->subEntities;
            if (is_array($subEntities)) {
                foreach ($subEntities as $subEntity) {
                    if (is_numeric($subEntity->depth)) {
                        //On prend la profondeur la plus élevée par entité
                        $depthByEntities[$type] = max($subEntity->depth, $depthByEntities[$type]);
                    }
                }
            }
        }
        return $depthByEntities;
    }
}
