<?php
declare(strict_types=1);

use App\Core\Auth;
use App\Core\Csrf;
use App\Core\Session;
use App\Core\View;

function config(?string $key = null, mixed $default = null): mixed
{
    static $config;
    if ($config === null) {
        $config = require CONFIG_PATH . '/config.php';
    }
    if ($key === null) {
        return $config;
    }
    $bits = explode('.', $key);
    $value = $config;
    foreach ($bits as $bit) {
        if (!is_array($value) || !array_key_exists($bit, $value)) {
            return $default;
        }
        $value = $value[$bit];
    }
    return $value;
}

function e(?string $value): string
{
    return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function url(string $path = ''): string
{
    $base = rtrim((string) config('app.base_path', ''), '/');
    $path = '/' . ltrim($path, '/');
    if ($path === '/') {
        return $base . '/';
    }
    return $base . $path;
}

function asset(string $path): string
{
    $path = ltrim($path, '/');
    $url = url('assets/' . $path);
    $full = BASE_PATH . '/assets/' . $path;
    if (is_file($full)) {
        $url .= '?v=' . filemtime($full);
    }
    return $url;
}

function redirect(string $path, int $code = 302): never
{
    header('Location: ' . url($path), true, $code);
    exit;
}

function old(string $key, mixed $default = ''): mixed
{
    $flash = Session::get('_old') ?? [];
    return $flash[$key] ?? $default;
}

function csrf_token(): string
{
    return Csrf::token();
}

function csrf_field(): string
{
    return '<input type="hidden" name="_csrf" value="' . e(Csrf::token()) . '">';
}

function auth(): Auth
{
    return Auth::instance();
}

function current_user(): ?array
{
    return Auth::instance()->user();
}

function current_tenant(): ?array
{
    return Auth::instance()->tenant();
}

function user_name(): string
{
    $u = current_user();
    if (!$u) {
        return '';
    }
    return trim(($u['first_name'] ?? '') . ' ' . ($u['last_name'] ?? ''));
}

function user_initials(): string
{
    $u = current_user();
    if (!$u) {
        return '?';
    }
    $a = mb_substr((string) ($u['first_name'] ?? ''), 0, 1);
    $b = mb_substr((string) ($u['last_name'] ?? ''), 0, 1);
    return strtoupper($a . $b);
}

function name_initials(?string $name): string
{
    $name = trim((string) $name);
    if ($name === '') {
        return '?';
    }
    $parts = preg_split('/\s+/', $name) ?: [];
    $a = mb_substr((string) ($parts[0] ?? ''), 0, 1);
    $b = mb_substr((string) ($parts[1] ?? $parts[0] ?? ''), 0, 1);
    if (($parts[1] ?? '') === '') {
        $b = mb_substr((string) ($parts[0] ?? ''), 1, 1);
    }
    return strtoupper($a . $b) ?: '?';
}

function merge_readable(?string $text): string
{
    return \App\Services\MergeService::readable((string) $text);
}

function has_role(string ...$codes): bool
{
    return Auth::instance()->hasRole(...$codes);
}

function can_see(string $module): bool
{
    return Auth::instance()->canSee($module);
}

function can_edit(string $module): bool
{
    return Auth::instance()->canEdit($module);
}

/** @return list<string> */
function add_new_modules(): array
{
    return ['leads', 'accounts', 'opportunities', 'tasks', 'emails'];
}

function can_add_new(): bool
{
    foreach (add_new_modules() as $module) {
        if (can_edit($module)) {
            return true;
        }
    }
    return false;
}

function industry(): array
{
    return \App\Industry\Catalog::current();
}

function industry_code(): string
{
    return \App\Industry\Catalog::currentCode();
}

function opportunity_label(): string
{
    return \App\Services\SetupService::label(
        'opportunities',
        (string) (industry()['opportunity_label'] ?? 'Opportunities')
    );
}

/** Proposal or agreement file required when moving into that stage. */
function stage_paper_kind(?array $stage): string
{
    if (!$stage) {
        return '';
    }
    $hay = strtolower(trim((string) ($stage['code'] ?? '') . ' ' . (string) ($stage['name'] ?? '')));
    if ($hay === '') {
        return '';
    }
    if (str_contains($hay, 'agreement')) {
        return 'agreement';
    }
    if (str_contains($hay, 'proposal')) {
        return 'proposal';
    }
    return '';
}

function stage_paper_label(string $kind): string
{
    return match ($kind) {
        'agreement' => 'Agreement',
        'proposal' => 'Proposal',
        default => '',
    };
}

function account_label(): string
{
    return \App\Services\SetupService::label(
        'accounts',
        (string) (industry()['account_label'] ?? 'Accounts')
    );
}

function noun(string $key, ?string $fallback = null): string
{
    return \App\Services\SetupService::label($key, $fallback);
}

/** @return list<array<string, mixed>> */
function entity_fields(string $entity): array
{
    return \App\Services\SetupService::fieldsFor($entity);
}

function show_field(string $form, string $field): bool
{
    return \App\Services\SetupService::formShows($form, $field);
}

function smart_tips_on(): bool
{
    return \App\Services\SmartTipsService::enabled();
}

function dash_on(string $card): bool
{
    $role = 'salesperson';
    if (has_role('manager')) {
        $role = 'manager';
    } elseif (has_role('service')) {
        $role = 'service';
    } elseif (has_role('admin')) {
        $role = 'admin';
    } elseif (has_role('marketing')) {
        $role = 'marketing';
    }
    return \App\Services\SetupService::dashOn($role, $card);
}

function json_attr(mixed $raw): array
{
    return \App\Services\IndustryService::decodeAttrs($raw);
}

function flash(string $key, ?string $message = null): ?string
{
    if ($message !== null) {
        Session::flash($key, $message);
        return null;
    }
    return Session::getFlash($key);
}

function money_fmt(float|int|string|null $amount): string
{
    return '$' . number_format((float) $amount, 0);
}

function money_compact(float|int|string|null $amount): string
{
    $n = (float) $amount;
    $abs = abs($n);
    if ($abs >= 1000000) {
        $v = $n / 1000000;
        $dec = abs($v - round($v)) < 0.005 ? 0 : 2;
        return '$' . number_format($v, $dec) . 'M';
    }
    if ($abs >= 10000) {
        return '$' . number_format($n / 1000, 0) . 'K';
    }
    return money_fmt($n);
}

/** @return array{pct:int,up:bool,show:bool} */
function delta_vs(int|float $current, int|float $previous): array
{
    $current = (float) $current;
    $previous = (float) $previous;
    if ($previous <= 0) {
        return ['pct' => $current > 0 ? 100 : 0, 'up' => $current >= 0, 'show' => $current > 0];
    }
    $change = (($current - $previous) / $previous) * 100;
    return ['pct' => (int) abs(round($change)), 'up' => $change >= 0, 'show' => true];
}

function relative_when(?string $datetime): string
{
    if (!$datetime) {
        return '—';
    }
    $ts = strtotime($datetime);
    if (!$ts) {
        return '—';
    }
    $day = date('Y-m-d', $ts);
    if ($day === date('Y-m-d')) {
        return date('g:i A', $ts);
    }
    if ($day === date('Y-m-d', strtotime('-1 day'))) {
        return 'Yesterday';
    }
    return date('M j', $ts);
}

/** @return array{0:string,1:string} icon class, color */
function activity_style(?string $type): array
{
    return match ($type) {
        'email' => ['bi-envelope', '#3b82f6'],
        'call' => ['bi-telephone', '#14b8a6'],
        'meeting' => ['bi-calendar-event', '#f97316'],
        'note' => ['bi-sticky', '#eab308'],
        'visit' => ['bi-geo-alt', '#8b5cf6'],
        'campaign' => ['bi-megaphone', '#ec4899'],
        'task' => ['bi-check2-square', '#22c55e'],
        'stage' => ['bi-flag', '#1a56c8'],
        default => ['bi-chat-dots', '#6b7280'],
    };
}

function meeting_kind(array $m): string
{
    $loc = (string) ($m['location'] ?? '');
    $low = strtolower($loc);
    $type = (string) ($m['meeting_type'] ?? 'meeting');
    if (str_contains($low, 'zoom') || str_contains($low, 'teams')) {
        return ($loc !== '' ? $loc : 'Video') . ' Meeting';
    }
    if ($type === 'call' || str_contains($low, 'phone')) {
        return 'Phone Call';
    }
    if ($type === 'visit') {
        return $loc !== '' ? $loc : 'Site visit';
    }
    return $loc !== '' ? $loc : 'Meeting';
}

function dt_fmt(?string $datetime, string $format = 'M j, Y'): string
{
    if (!$datetime) {
        return '—';
    }
    $ts = strtotime($datetime);
    return $ts ? date($format, $ts) : '—';
}

function due_label(?string $datetime, ?string $status = 'open'): array
{
    if ($status === 'completed') {
        return ['text' => 'Done', 'class' => 'due-done'];
    }
    if ($status === 'cancelled') {
        return ['text' => 'Cancelled', 'class' => 'due-cancelled'];
    }
    if (!$datetime) {
        return ['text' => '', 'class' => ''];
    }
    $ts = strtotime($datetime);
    if (!$ts) {
        return ['text' => '', 'class' => ''];
    }
    $dueDay = date('Y-m-d', $ts);
    $today = date('Y-m-d');
    if ($dueDay < $today) {
        return ['text' => date('M j', $ts), 'class' => 'due-overdue'];
    }
    if ($dueDay === $today) {
        return ['text' => 'Today', 'class' => 'due-today'];
    }
    return ['text' => date('M j', $ts), 'class' => 'due-later'];
}

function visit_attended(array $row): bool
{
    $next = $row['next_visit_at'] ?? null;
    if ($next !== null && $next !== '') {
        return false;
    }
    $last = $row['last_visit_at'] ?? null;
    return $last !== null && $last !== '';
}

/** @return array{text: string, class: string, tone: string} */
function visit_status_label(array $row): array
{
    if (visit_attended($row)) {
        return ['text' => 'Attended', 'class' => 'due-done', 'tone' => 'done'];
    }
    $due = due_label($row['next_visit_at'] ?? null);
    return match ($due['class'] ?? '') {
        'due-overdue' => ['text' => 'Late', 'class' => 'due-overdue', 'tone' => 'late'],
        'due-today' => ['text' => 'Today', 'class' => 'due-today', 'tone' => 'today'],
        default => ['text' => 'Coming up', 'class' => $due['class'] !== '' ? $due['class'] : 'due-later', 'tone' => 'soon'],
    };
}

function visit_subject(array $row): string
{
    $subject = trim((string) ($row['visit_subject'] ?? ''));
    if ($subject !== '') {
        return $subject;
    }
    $name = trim((string) ($row['name'] ?? ''));
    return $name !== '' ? $name : 'Visit';
}

/** @return array{text: string, class: string, tone: string} */
function job_status_label(string $status): array
{
    return match ($status) {
        'in_progress' => ['text' => 'In progress', 'class' => 'due-later', 'tone' => 'progress'],
        'done' => ['text' => 'Done', 'class' => 'due-done', 'tone' => 'done'],
        'cancelled' => ['text' => 'Cancelled', 'class' => 'due-cancelled', 'tone' => 'cancelled'],
        default => ['text' => 'Open', 'class' => 'due-window', 'tone' => 'open'],
    };
}

/** @return array{text: string, class: string, tone: string} */
function equipment_status_label(string $status): array
{
    return $status === 'retired'
        ? ['text' => 'Retired', 'class' => 'due-cancelled', 'tone' => 'retired']
        : ['text' => 'Active', 'class' => 'due-done', 'tone' => 'active'];
}

/**
 * Status chip + left-border tone for quotes, invoices, contracts, orders, documents.
 * @return array{text: string, class: string, tone: string, rec: string}
 */
function commerce_status_label(string $status, ?string $text = null): array
{
    $row = match ($status) {
        'pending_approval', 'pending', 'partial', 'renewing' => ['class' => 'due-later', 'tone' => 'progress', 'rec' => 'progress'],
        'approved', 'sent', 'paid', 'active', 'fulfilled' => ['class' => 'due-done', 'tone' => 'done', 'rec' => 'won'],
        'signed' => ['class' => 'due-done', 'tone' => 'signed', 'rec' => 'signed'],
        'rejected', 'void', 'cancelled', 'declined', 'expired', 'retired' => ['class' => 'due-cancelled', 'tone' => 'cancelled', 'rec' => 'lost'],
        'unpaid' => ['class' => 'due-overdue', 'tone' => 'late', 'rec' => 'lost'],
        default => ['class' => 'due-window', 'tone' => 'open', 'rec' => 'open'],
    };
    $row['text'] = $text !== null && $text !== ''
        ? $text
        : ucfirst(str_replace('_', ' ', $status));
    return $row;
}

function related_url(?string $type, ?int $id): string
{
    if (!$type || !$id) {
        return '#';
    }
    $map = [
        'account' => 'accounts/' . $id,
        'contact' => 'contacts/' . $id,
        'lead' => 'leads/' . $id,
        'opportunity' => 'opportunities/' . $id,
        'task' => 'tasks',
        'quote' => 'quotes/' . $id,
        'order' => 'orders',
        'contract' => 'contracts/' . $id,
        'invoice' => 'invoices/' . $id,
        'job' => 'jobs/' . $id,
    ];
    return url($map[$type] ?? '#');
}

function json_out(array $payload, int $code = 200): never
{
    while (ob_get_level() > 0) {
        ob_end_clean();
    }
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($payload, JSON_UNESCAPED_UNICODE);
    exit;
}

function view(string $template, array $data = [], ?string $layout = 'layouts/app'): void
{
    View::render($template, $data, $layout);
}

function request_ip(): string
{
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

function is_ajax(): bool
{
    return strtolower($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'xmlhttprequest'
        || str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json');
}

function attached_file_chip(?string $fileName, string $href): string
{
    $fileName = trim((string) $fileName);
    if ($fileName === '' || $href === '' || $href === '#') {
        return '';
    }
    $ext = strtolower((string) pathinfo($fileName, PATHINFO_EXTENSION));
    $icon = match ($ext) {
        'pdf' => 'bi-file-earmark-pdf',
        'doc', 'docx' => 'bi-file-earmark-word',
        'jpg', 'jpeg', 'png' => 'bi-file-earmark-image',
        default => 'bi-paperclip',
    };
    return '<a class="task-file" href="' . e($href) . '" target="_blank" rel="noopener noreferrer"><i class="bi ' . e($icon) . '"></i> ' . e($fileName) . '</a>';
}

function task_file_chip(?string $fileName, int $taskId): string
{
    return attached_file_chip($fileName, url('tasks/' . $taskId . '/file'));
}

function activity_file_chip(?string $fileName, int $activityId): string
{
    return attached_file_chip($fileName, url('activities/' . $activityId . '/file'));
}

function document_file_chip(?string $fileName, int $documentId): string
{
    return attached_file_chip($fileName, url('documents/' . $documentId . '/file'));
}
