<?php

namespace App\Actions\Shifts;

use App\Exceptions\ShiftAlreadyOpen;
use App\Exceptions\TerminalShiftOpen;
use App\Models\Shift;
use App\Models\Store;
use App\Models\User;
use Illuminate\Support\Facades\DB;

/**
 * Open a shift for the given cashier on the given store.
 *
 * Slice 1 scopes uniqueness by (store, user) — one open shift per
 * cashier per store. Terminal binding lands later when we surface a
 * terminal picker on the cashier screen.
 *
 * Hooks:
 *   - action `shift.before_open` → ($data, $user, $store)
 *   - action `shift.after_open`  → ($shift)
 */
class OpenShift
{
    /**
     * @param array<string, mixed> $data {
     *   opening_cash: numeric-string|float,
     *   notes?: ?string,
     *   terminal_id?: ?int,
     *   opening_denominations?: array<string, int>|null,
     * }
     */
    public function __invoke(array $data, User $user, Store $store): Shift
    {
        do_action('shift.before_open', $data, $user, $store);

        $terminalId = isset($data['terminal_id']) && $data['terminal_id'] !== ''
            ? (int) $data['terminal_id']
            : null;

        return DB::transaction(function () use ($data, $user, $store, $terminalId) {
            // One open shift per (store, user) at a time. Lock the
            // candidate row to keep two clicks from racing each other.
            $existing = Shift::query()
                ->where('store_id', $store->id)
                ->where('user_id', $user->id)
                ->where('status', Shift::STATUS_OPEN)
                ->lockForUpdate()
                ->first();

            if ($existing) {
                throw new ShiftAlreadyOpen($existing);
            }

            // One open shift per terminal (Slice B). A second cashier
            // can't run a parallel drawer on the same physical till —
            // they take over via handover (close + reopen).
            if ($terminalId) {
                $terminalShift = Shift::query()
                    ->where('terminal_id', $terminalId)
                    ->where('status', Shift::STATUS_OPEN)
                    ->lockForUpdate()
                    ->first();

                if ($terminalShift) {
                    throw new TerminalShiftOpen($terminalShift);
                }
            }

            $shift = new Shift();
            $shift->fill([
                'store_id'              => (int) $store->id,
                'terminal_id'           => $terminalId,
                'user_id'               => (int) $user->id,
                'opened_at'             => now(),
                'opening_cash'          => $this->fmt((string) ($data['opening_cash'] ?? '0')),
                'opening_denominations' => $data['opening_denominations'] ?? null,
                'notes'                 => $data['notes'] ?? null,
            ]);
            $shift->forceFill([
                'status' => Shift::STATUS_OPEN,
            ]);
            $shift->save();

            do_action('shift.after_open', $shift);

            return $shift->fresh();
        });
    }

    private function fmt(string $v): string
    {
        if ($v === '' || $v === '-' || $v === '.') return '0.0000';
        return bcadd($v, '0', 4);
    }
}
