<?php
namespace App\Http\Controllers;
use App\Services\ApprovalService;
use App\Services\SequenceService;
use App\Support\CrudHooks;
use App\Support\CrudRegistry;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;

class CrudController extends Controller {
    protected function needPerm(array $cfg, string $action): void {
        $u = Auth::user();
        abort_unless($u && $u->canDo($cfg['perm'].'.'.$action), 403, 'No permission: '.$cfg['perm'].'.'.$action);
    }
    public function index(Request $r, string $key) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'view');
        $model = $cfg['model'];
        $q = $model::query();
        $with = [];
        foreach (array_keys($cfg['columns']) as $c) if (str_contains($c, '.')) $with[] = explode('.', $c)[0];
        if ($with) $q->with(array_unique($with));
        if (($s = trim((string) $r->get('q', ''))) !== '' && !empty($cfg['search'])) {
            $q->where(function ($w) use ($cfg, $s) {
                foreach ($cfg['search'] as $i => $f) {
                    $m = $i === 0 ? 'where' : 'orWhere';
                    if (str_contains($f, '.')) { [$rel, $col] = explode('.', $f, 2); $w->{$m.'Has'}($rel, fn($x) => $x->where($col, 'like', "%{$s}%")); }
                    else $w->$m($f, 'like', "%{$s}%");
                }
            });
        }
        $activeFilters = [];
        foreach ($cfg['fields'] as $name => $f) {
            if (!empty($f['filter']) && $r->filled($name)) { $q->where($name, $r->get($name)); $activeFilters[$name] = $r->get($name); }
        }
        $sort = $cfg['sort'] ?? '-id';
        $q->orderBy(ltrim($sort, '-'), str_starts_with($sort, '-') ? 'desc' : 'asc');
        $rows = $q->paginate(20)->withQueryString();
        $options = $this->selectOptions($cfg);
        return view('crud.index', compact('cfg', 'key', 'rows', 'options', 'activeFilters'));
    }
    /** JSON form schema for dashboard inline-create (A2). */
    public function schema(string $key) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'create');
        abort_if(!empty($cfg['no_create']) || !empty($cfg['dedicated']), 403, 'Inline create not available for ' . $cfg['title']);
        $options = $this->selectOptions($cfg);
        $auto = CrudHooks::autonumber()[$key] ?? [];
        $fields = [];
        foreach ($cfg['fields'] as $name => $f) {
            $type = $f['type'] ?? 'text';
            if ($type === 'display') continue;
            $spec = ['name' => $name, 'label' => $f['label'] ?? $name, 'type' => $type,
                'required' => !empty($f['required']), 'width' => $f['width'] ?? 6,
                'placeholder' => $f['placeholder'] ?? '', 'default' => $f['default'] ?? null,
                'auto' => array_key_exists($name, $auto)];
            if ($type === 'textarea') $spec['rows'] = $f['rows'] ?? 3;
            if ($type === 'select') {
                $opts = $f['options'] ?? [];
                if (isset($options[$name])) $opts = $options[$name];
                $spec['options'] = collect($opts)->map(fn($l, $v) => ['v' => $v, 'l' => (string) $l])->values()->all();
            }
            $fields[] = $spec;
        }
        return response()->json(['key' => $key, 'title' => $cfg['title'], 'singular' => $cfg['singular'],
            'createUrl' => crud_url($key, 'store'), 'fields' => $fields]);
    }
    public function create(string $key) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'create');
        abort_if(!empty($cfg['no_create']), 403);
        if (!empty($cfg['dedicated'])) return redirect()->to(crud_url($key, 'create'));
        $record = new ($cfg['model']);
        foreach ($cfg['fields'] as $name => $f) {
            if (array_key_exists('default', $f)) $record->$name = $f['default'] === 'today' ? date('Y-m-d') : $f['default'];
        }
        $options = $this->selectOptions($cfg);
        return view('crud.form', ['cfg' => $cfg, 'key' => $key, 'record' => $record, 'options' => $options, 'isNew' => true]);
    }
    public function store(Request $r, string $key) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'create');
        abort_if(!empty($cfg['no_create']) || !empty($cfg['dedicated']), 403);
        $data = $r->validate($this->rules($cfg));
        $data = $this->normalize($cfg, $r, $data, null);
        foreach (CrudHooks::autonumber()[$key] ?? [] as $field => $seq) {
            if (empty($data[$field])) $data[$field] = SequenceService::next($seq);
        }
        $data = CrudHooks::saving($key, $data, null);
        $data = $this->stamps($cfg, $data);
        $record = ($cfg['model'])::create($data);
        CrudHooks::posted($key, $record, true);
        if (!empty($cfg['approval_type']) && in_array($record->status ?? '', ['pending', 'submitted', 'draft', 'sent'], true)) {
            ApprovalService::request($cfg['approval_type'], $record->id);
        }
        if ($r->wantsJson() || $r->expectsJson()) {
            return response()->json(['ok' => true, 'id' => $record->id,
                'url' => crud_url($key, 'show', $record->id), 'label' => $cfg['singular'] . ' created.']);
        }
        return redirect()->to(crud_url($key, 'show', $record->id))->with('success', $cfg['singular'].' created.');
    }
    public function show(string $key, int $id) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'view');
        $with = [];
        foreach (array_keys($cfg['columns']) as $c) if (str_contains($c, '.')) $with[] = explode('.', $c)[0];
        $record = ($cfg['model'])::with(array_unique($with))->findOrFail($id);
        $pendingApproval = !empty($cfg['approval_type']) ? ApprovalService::pendingFor($cfg['approval_type'], $id) : null;
        $lines = method_exists($record, 'lines') ? $record->lines()->get() : collect();
        $parts = method_exists($record, 'parts') ? $record->parts()->with('item')->get() : collect();
        return view('crud.show', compact('cfg', 'key', 'record', 'pendingApproval', 'lines', 'parts'));
    }
    public function edit(string $key, int $id) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'edit');
        abort_if(!empty($cfg['no_edit']), 403);
        if (!empty($cfg['dedicated'])) return redirect()->to(crud_url($key, 'edit', $id));
        $record = ($cfg['model'])::findOrFail($id);
        $options = $this->selectOptions($cfg);
        return view('crud.form', ['cfg' => $cfg, 'key' => $key, 'record' => $record, 'options' => $options, 'isNew' => false]);
    }
    public function update(Request $r, string $key, int $id) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'edit');
        abort_if(!empty($cfg['no_edit']) || !empty($cfg['dedicated']), 403);
        $record = ($cfg['model'])::findOrFail($id);
        $data = $r->validate($this->rules($cfg, $id));
        foreach ($cfg['fields'] as $name => $f) if (!empty($f['readonly_on_edit'])) unset($data[$name]);
        $data = $this->normalize($cfg, $r, $data, $record);
        $data = CrudHooks::saving($key, $data, $record);
        $record->update($data);
        CrudHooks::posted($key, $record, false);
        return redirect()->to(crud_url($key, 'show', $record->id))->with('success', $cfg['singular'].' updated.');
    }
    public function destroy(string $key, int $id) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'delete');
        abort_if(!empty($cfg['no_delete']), 403);
        $record = ($cfg['model'])::findOrFail($id);
        if ((!empty($cfg['dedicated']) || !empty($cfg['approval_type'])) && isset($record->status)) {
            abort_unless(in_array($record->status, ['draft', 'pending', 'rejected', 'cancelled', 'declined'], true), 403, 'Only draft/pending records can be deleted.');
        }
        $record->delete();
        return redirect()->to(crud_url($key, 'index'))->with('success', $cfg['singular'].' deleted.');
    }
    public function transition(Request $r, string $key, int $id, string $action) {
        $cfg = CrudRegistry::get($key);
        $this->needPerm($cfg, 'approve');
        $t = $cfg['transitions'][$action] ?? abort(404);
        $record = ($cfg['model'])::findOrFail($id);
        abort_unless(in_array($record->status, $t['from'], true), 403, 'Transition not allowed from '.$record->status);
        if (!empty($cfg['approval_type']) && ($ap = ApprovalService::pendingFor($cfg['approval_type'], $id)) && in_array($t['to'], ['approved', 'rejected', 'declined'], true)) {
            ApprovalService::decide($ap, $t['to'] === 'approved' ? 'approved' : 'rejected', $r->get('remarks'));
        } else {
            $record->status = $t['to'];
            try { if (Schema::hasColumn($record->getTable(), 'approved_by')) $record->approved_by = Auth::id(); } catch (\Throwable $e) {}
            $record->save();
            CrudHooks::transition($key, $record, $t['to']);
        }
        return back()->with('success', $cfg['singular'].' → '.$t['to']);
    }
    protected function rules(array $cfg, $id = null): array {
        $rules = [];
        foreach ($cfg['fields'] as $name => $f) {
            $type = $f['type'] ?? 'text';
            if ($type === 'display') continue;
            if (!empty($f['readonly_on_edit']) && $id) continue;
            if ($type === 'checkbox') { $rules[$name] = ['nullable', 'boolean']; continue; }
            $r = [!empty($f['required']) ? 'required' : 'nullable'];
            if ($type === 'number') $r[] = 'integer';
            elseif ($type === 'decimal') $r[] = 'numeric';
            elseif (in_array($type, ['date', 'datetime', 'month'])) $r[] = 'date';
            elseif ($type === 'time') $r[] = 'date_format:H:i';
            elseif ($type === 'email') $r[] = 'email';
            elseif ($type === 'file') $r[] = 'file|max:5120';
            if (!empty($f['model'])) $r[] = 'exists:'.(new $f['model'])->getTable().',id';
            if (!empty($f['options'])) $r[] = 'in:'.implode(',', array_keys($f['options']));
            if (isset($f['min'])) $r[] = 'min:'.$f['min'];
            if (isset($f['max'])) $r[] = 'max:'.$f['max'];
            if (!empty($f['unique'])) { [$t, $c] = explode(',', $f['unique']); $r[] = 'unique:'.$t.','.$c.($id ? ','.$id : ''); }
            $rules[$name] = $r;
        }
        return array_merge($rules, $cfg['rules'] ?? []);
    }
    protected function normalize(array $cfg, Request $r, array $data, $record): array {
        foreach ($cfg['fields'] as $name => $f) {
            $type = $f['type'] ?? 'text';
            if ($type === 'display') { unset($data[$name]); continue; }
            if ($type === 'checkbox') { $data[$name] = (int) $r->boolean($name); continue; }
            if ($type === 'file') {
                if ($r->hasFile($name)) $data[$name] = $r->file($name)->store('uploads', 'public');
                else unset($data[$name]);
                continue;
            }
            if (($data[$name] ?? null) === '' && empty($f['required'])) $data[$name] = null;
        }
        return $data;
    }
    protected function stamps(array $cfg, array $data): array {
        try {
            $table = (new ($cfg['model']))->getTable();
            foreach (['created_by', 'requested_by', 'prepared_by'] as $col) {
                if (Schema::hasColumn($table, $col) && empty($data[$col])) $data[$col] = Auth::id();
            }
        } catch (\Throwable $e) {}
        return $data;
    }
    public function selectOptions(array $cfg): array {
        $out = [];
        foreach ($cfg['fields'] as $name => $f) {
            if (($f['type'] ?? '') !== 'select' || empty($f['model'])) continue;
            try {
                $m = $f['model'];
                $q = $m::query();
                foreach ((array) ($f['where'] ?? []) as $c => $v) $q->where($c, $v);
                $lf = $f['label_field'] ?? 'display_name';
                $rows = $q->limit(2000)->get();
                $out[$name] = $rows->mapWithKeys(fn($x) => [$x->id => (string) ($lf === 'display_name' ? $x->display_name : $x->$lf)])->all();
            } catch (\Throwable $e) { $out[$name] = []; }
        }
        return $out;
    }
}
