···1+<?php
2+3+namespace SocialDept\AtpParity\Sync;
4+5+use Illuminate\Database\Eloquent\Model;
6+use SocialDept\AtpParity\Concerns\SyncsWithAtp;
7+use SocialDept\AtpSchema\Data\Data;
8+9+/**
10+ * Detects conflicts between local and remote record versions.
11+ */
12+class ConflictDetector
13+{
14+ /**
15+ * Check if there's a conflict between local model and remote record.
16+ */
17+ public function hasConflict(Model $model, Data $record, string $cid): bool
18+ {
19+ // No conflict if model doesn't have local changes
20+ if (! $this->modelHasLocalChanges($model)) {
21+ return false;
22+ }
23+24+ // No conflict if CID matches (same version)
25+ if ($this->getCid($model) === $cid) {
26+ return false;
27+ }
28+29+ return true;
30+ }
31+32+ /**
33+ * Check if the model has local changes since last sync.
34+ */
35+ protected function modelHasLocalChanges(Model $model): bool
36+ {
37+ // Use trait method if available
38+ if ($this->usesTrait($model, SyncsWithAtp::class)) {
39+ return $model->hasLocalChanges();
40+ }
41+42+ // Fallback: compare updated_at with a sync timestamp if available
43+ $syncedAt = $model->getAttribute('atp_synced_at');
44+45+ if (! $syncedAt) {
46+ return true;
47+ }
48+49+ $updatedAt = $model->getAttribute('updated_at');
50+51+ if (! $updatedAt) {
52+ return false;
53+ }
54+55+ return $updatedAt > $syncedAt;
56+ }
57+58+ /**
59+ * Get the CID from a model.
60+ */
61+ protected function getCid(Model $model): ?string
62+ {
63+ $column = config('parity.columns.cid', 'atp_cid');
64+65+ return $model->getAttribute($column);
66+ }
67+68+ /**
69+ * Check if a model uses a specific trait.
70+ *
71+ * @param class-string $trait
72+ */
73+ protected function usesTrait(Model $model, string $trait): bool
74+ {
75+ return in_array($trait, class_uses_recursive($model));
76+ }
77+}