<?php
/**
 * TripStops — planning stops on a Trip Request and execution stops on a Trip.
 * One shared table with two mutually-exclusive parent columns. On dispatch
 * the Trips model copies the request's stops into new rows tied to the new
 * trip_id (see Trips::copyStopsFromRequest, called from dispatchFromRequest).
 */
class TripStops extends Model
{
    private function s($v): string { return trim((string)$v); }
    private function f($v): ?float { $v = str_replace(',', '', (string)$v); return $v === '' ? null : (float)$v; }
    private function nn(int $v): ?int { return $v > 0 ? $v : null; }

    public const KINDS = [
        'pickup'         => 'Pickup',
        'delivery'       => 'Delivery',
        'waypoint'       => 'Waypoint',
        'weighbridge'    => 'Weighbridge',
        'fuel_stop'      => 'Fuel stop',
        'border'         => 'Border / customs',
        'overnight_rest' => 'Overnight rest',
        'customer_visit' => 'Customer visit',
    ];

    public function forRequest(int $requestId): array
    {
        return (new DB())->mySelect(
            "SELECT * FROM trip_stops
              WHERE trip_request_id = :id AND deleted = 0
           ORDER BY seq_no ASC, c0d ASC",
            [':id' => $requestId]
        ) ?: [];
    }

    public function forTrip(int $tripId): array
    {
        return (new DB())->mySelect(
            "SELECT * FROM trip_stops
              WHERE trip_id = :id AND deleted = 0
           ORDER BY seq_no ASC, c0d ASC",
            [':id' => $tripId]
        ) ?: [];
    }

    public function byId(int $id): array
    {
        $rs = (new DB())->mySelect(
            "SELECT * FROM trip_stops WHERE c0d = :id AND deleted = 0 LIMIT 1",
            [':id' => $id]
        );
        return $rs[0] ?? [];
    }

    private function nextSeq(?int $reqId, ?int $tripId): int
    {
        $where = $reqId ? "trip_request_id = :p" : "trip_id = :p";
        $rs = (new DB())->mySelect(
            "SELECT COALESCE(MAX(seq_no),0) + 1 AS n FROM trip_stops WHERE $where AND deleted = 0",
            [':p' => $reqId ?: $tripId]
        );
        return (int)($rs[0]['n'] ?? 1);
    }

    /**
     * Create a stop. Exactly one of ($requestId, $tripId) must be non-zero.
     */
    public function create(int $requestId, int $tripId, array $d, int $uid): array
    {
        if (!$requestId && !$tripId) return ['ok' => false, 'msg' => 'Missing parent (request or trip).', 'id' => 0];
        if ($requestId && $tripId)   return ['ok' => false, 'msg' => 'A stop cannot belong to both a request and a trip.', 'id' => 0];

        $kind = $this->s(($d['kind'] ?? '') ?: 'waypoint');
        if (!isset(self::KINDS[$kind])) return ['ok' => false, 'msg' => 'Unknown stop kind.', 'id' => 0];

        $seq = !empty($d['seq_no']) ? (int)$d['seq_no'] : $this->nextSeq($requestId ?: null, $tripId ?: null);

        $r = (new DB())->insertUpdate(
            "INSERT INTO trip_stops
                (tenant_id, trip_request_id, trip_id, seq_no, kind,
                 location_name, address, city, country, lat, lng,
                 contact_name, contact_phone,
                 planned_arrival, planned_departure, stop_notes,
                 created_by, created_on)
             VALUES
                (:tenant, :req, :trp, :sq, :k,
                 :ln, :ad, :ci, :co, :lat, :lng,
                 :cn, :cp,
                 :pa, :pd, :nt,
                 :uid, NOW())",
            [
                ':tenant' => defined('DEFAULT_TENANT_ID') ? DEFAULT_TENANT_ID : 1,
                ':req'    => $this->nn($requestId),
                ':trp'    => $this->nn($tripId),
                ':sq'     => $seq,
                ':k'      => $kind,
                ':ln'     => $this->s($d['location_name'] ?? ''),
                ':ad'     => $this->s($d['address']       ?? ''),
                ':ci'     => $this->s($d['city']          ?? ''),
                ':co'     => $this->s($d['country']       ?? ''),
                ':lat'    => $this->f($d['lat']           ?? ''),
                ':lng'    => $this->f($d['lng']           ?? ''),
                ':cn'     => $this->s($d['contact_name']  ?? ''),
                ':cp'     => $this->s($d['contact_phone'] ?? ''),
                ':pa'     => !empty($d['planned_arrival'])   ? $this->s($d['planned_arrival'])   : null,
                ':pd'     => !empty($d['planned_departure']) ? $this->s($d['planned_departure']) : null,
                ':nt'     => $this->s($d['stop_notes'] ?? ''),
                ':uid'    => $uid,
            ]
        );
        return ['ok' => $r['success'] > 0, 'msg' => $r['success'] > 0 ? 'Stop added.' : 'Save failed.', 'id' => (int)$r['last_id']];
    }

    public function softDelete(int $id, int $uid): array
    {
        (new DB())->insertUpdate(
            "UPDATE trip_stops SET deleted = 1, edited_by = :uid, edited_on = NOW() WHERE c0d = :id",
            [':uid' => $uid, ':id' => $id]
        );
        return ['ok' => true, 'msg' => 'Stop removed.'];
    }

    public function markArrived(int $id, int $uid, ?int $odometer = null): array
    {
        (new DB())->insertUpdate(
            "UPDATE trip_stops SET actual_arrival = NOW(),
                                    arrival_odometer = COALESCE(:odo, arrival_odometer),
                                    edited_by = :uid, edited_on = NOW()
              WHERE c0d = :id AND deleted = 0",
            [':id' => $id, ':uid' => $uid, ':odo' => $odometer]
        );
        return ['ok' => true, 'msg' => 'Arrival marked.'];
    }

    public function markDeparted(int $id, int $uid, ?int $odometer = null): array
    {
        (new DB())->insertUpdate(
            "UPDATE trip_stops SET actual_departure = NOW(),
                                    departure_odometer = COALESCE(:odo, departure_odometer),
                                    edited_by = :uid, edited_on = NOW()
              WHERE c0d = :id AND deleted = 0",
            [':id' => $id, ':uid' => $uid, ':odo' => $odometer]
        );
        return ['ok' => true, 'msg' => 'Departure marked.'];
    }

    /**
     * Called from Trips::dispatchFromRequest — copies planning stops from a
     * request to newly-created execution stops on the trip. Original request
     * rows stay in place so "planned vs actual" is always visible.
     */
    public function copyRequestStopsToTrip(int $requestId, int $tripId, int $uid): int
    {
        $stops = $this->forRequest($requestId);
        $count = 0;
        foreach ($stops as $s) {
            (new DB())->insertUpdate(
                "INSERT INTO trip_stops
                    (tenant_id, trip_id, seq_no, kind,
                     location_name, address, city, country, lat, lng,
                     contact_name, contact_phone,
                     planned_arrival, planned_departure, stop_notes,
                     created_by, created_on)
                 VALUES
                    (:tenant, :trp, :sq, :k,
                     :ln, :ad, :ci, :co, :lat, :lng,
                     :cn, :cp,
                     :pa, :pd, :nt,
                     :uid, NOW())",
                [
                    ':tenant' => defined('DEFAULT_TENANT_ID') ? DEFAULT_TENANT_ID : 1,
                    ':trp'    => $tripId,
                    ':sq'     => (int)$s['seq_no'],
                    ':k'      => $s['kind'],
                    ':ln'     => $s['location_name'],
                    ':ad'     => $s['address'],
                    ':ci'     => $s['city'],
                    ':co'     => $s['country'],
                    ':lat'    => $s['lat'],
                    ':lng'    => $s['lng'],
                    ':cn'     => $s['contact_name'],
                    ':cp'     => $s['contact_phone'],
                    ':pa'     => $s['planned_arrival'],
                    ':pd'     => $s['planned_departure'],
                    ':nt'     => $s['stop_notes'],
                    ':uid'    => $uid,
                ]
            );
            $count++;
        }
        return $count;
    }
}
