<?php
declare(strict_types=1);

class MyTasksController
{
    /**
     * "Tasks" listing. Permission scope is the union of:
     *   - tasks the user is a project_members-member of, AND
     *   - tasks the user is directly assigned to (covers edge cases where
     *     somebody is assigned but no longer a project member).
     *
     * On top of that base, the assigned_to filter narrows to a specific
     * person, "no assignee" or "anyone". Default is the current user, so a
     * fresh visit to /my-tasks shows the user's own to-dos. The legacy
     * `scope` URL parameter (used by dashboard cards) is mapped to the new
     * filter so existing links keep working.
     */
    public static function index(): void
    {
        Auth::requireLogin();
        $db     = Database::getInstance();
        $userId = Auth::id();

        // ── Filter parsing ──────────────────────────────────────────────────
        $filterStatus   = Helper::get('status');
        $filterPriority = Helper::get('priority');
        $filterProject  = (int)Helper::get('project_id') ?: null;
        $filterDue      = Helper::get('due');
        $filterScope    = Helper::get('scope');             // legacy
        $filterClear    = Helper::get('clear', '0') === '1';

        // assigned_to URL semantics:
        //   not in URL              → default to "me"
        //   '__all__'               → no assignee filter (anyone)
        //   '_none'                 → tasks without any assignee
        //   numeric                 → that user's tasks
        // Distinguish "not in URL" from "explicit empty" via $_GET, because
        // Helper::get collapses both to ''.
        $assignedRaw = $_GET['assigned_to'] ?? null;

        if ($filterClear) {
            // "Filter entfernen": show every task the user is permitted to see.
            $filterStatus   = 'all';
            $filterPriority = '';
            $filterProject  = null;
            $filterDue      = '';
            $filterAssigned = '__all__';
            $filterScope    = '';
        } else {
            if ($filterStatus === '') $filterStatus = 'open';
            if ($assignedRaw === null) {
                // Legacy scope mapping: scope=all → __all__, anything else → me.
                $filterAssigned = $filterScope === 'all' ? '__all__' : (string)$userId;
            } else {
                $filterAssigned = (string)$assignedRaw;
            }
        }

        // ── Permission base ─────────────────────────────────────────────────
        // Either I'm a member of the project, or the task is directly assigned
        // to me. The OR keeps me from losing my own assignments if I get
        // removed from a project but the assignment record stays.
        $where = [
            '(EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = t.project_id AND pm.user_id = ?)
              OR EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id AND ta.user_id = ?))',
        ];
        $params = [$userId, $userId];

        // ── Assigned-to filter ──────────────────────────────────────────────
        // Accepted shapes:
        //   '__all__'         — anyone, no filter
        //   '_none'           — tasks without any assignee
        //   'u_<digits>'      — that user
        //   'e_<digits>'      — that external participant
        //   '<digits>'        — that user (legacy plain id, used by dashboard cards)
        if ($filterAssigned === '__all__') {
            // anyone — no extra filter
        } elseif ($filterAssigned === '_none') {
            $where[]  = 'NOT EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id)';
        } elseif (preg_match('/^u_(\d+)$/', $filterAssigned, $m)) {
            $where[]  = 'EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id AND ta.user_id = ?)';
            $params[] = (int)$m[1];
        } elseif (preg_match('/^e_(\d+)$/', $filterAssigned, $m)) {
            // Externals are project-scoped, so the permission base is
            // automatically respected: the OR(project_members, task_assignees)
            // floor only matches tasks the user can already see.
            $where[]  = 'EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id AND ta.external_id = ?)';
            $params[] = (int)$m[1];
        } elseif ($filterAssigned !== '' && ctype_digit($filterAssigned)) {
            // Legacy plain-id shape — treat as user_id.
            $where[]  = 'EXISTS (SELECT 1 FROM task_assignees ta WHERE ta.task_id = t.id AND ta.user_id = ?)';
            $params[] = (int)$filterAssigned;
        }

        // ── Status / priority / project / due filters ───────────────────────
        if ($filterStatus === 'open') {
            $where[] = 'ts.is_done = 0';
        } elseif ($filterStatus === 'done') {
            $where[] = 'ts.is_done = 1';
        }

        if ($filterPriority) {
            $where[]  = 't.priority = ?';
            $params[] = $filterPriority;
        }

        if ($filterProject) {
            $where[]  = 't.project_id = ?';
            $params[] = $filterProject;
        }

        if ($filterDue === 'today') {
            $where[] = "t.due_date = date('now')";
        } elseif ($filterDue === 'overdue') {
            $where[] = "t.due_date IS NOT NULL AND t.due_date < date('now')";
        }

        // ── Query ───────────────────────────────────────────────────────────
        $tasks = $db->fetchAll(
            'SELECT t.*, ts.name as status_name, ts.color as status_color, ts.is_done,
                    p.name as project_name, p.color as project_color, p.id as project_id
             FROM tasks t
             JOIN task_statuses ts ON ts.id = t.status_id
             JOIN projects p ON p.id = t.project_id
             WHERE ' . implode(' AND ', $where) . '
             ORDER BY ts.is_done ASC,
                      CASE WHEN t.due_date IS NULL THEN 1 ELSE 0 END,
                      t.due_date ASC,
                      CASE t.priority WHEN \'urgent\' THEN 0 WHEN \'high\' THEN 1 WHEN \'medium\' THEN 2 ELSE 3 END',
            $params
        );

        Helper::loadAssignees($db, $tasks);

        // ── Dropdown data ───────────────────────────────────────────────────
        $myProjects = $db->fetchAll(
            'SELECT p.id, p.name, p.color FROM projects p
             JOIN project_members pm ON pm.project_id = p.id AND pm.user_id = ?
             WHERE p.status = ? ORDER BY p.name',
            [$userId, 'active']
        );

        // The assigned-to filter is now a server-side type-ahead widget
        // (`/api/tasks/assignee-search`) so the full reachable user/external
        // directory no longer ships into the page. We only need to resolve
        // the *currently selected* filter value to a display label so the
        // input does not show "u_42" but the actual person's name.
        $assignedDisplay = self::resolveAssignedDisplay((string)$filterAssigned, $userId, $db);

        // Group tasks by project for display
        $grouped = [];
        foreach ($tasks as $task) {
            $grouped[$task['project_id']]['project'] = [
                'id'    => $task['project_id'],
                'name'  => $task['project_name'],
                'color' => $task['project_color'],
            ];
            $grouped[$task['project_id']]['tasks'][] = $task;
        }

        $pageTitle = t('tasks');
        view('my-tasks/index', compact(
            'tasks', 'grouped', 'myProjects',
            'filterStatus', 'filterPriority', 'filterProject', 'filterDue',
            'filterAssigned', 'filterScope', 'assignedDisplay', 'pageTitle'
        ));
    }

    /**
     * Map a filter value (`u_42` / `e_17` / `__all__` / `_none`) to the human
     * label shown inside the assigned-to text input. Permission-bound where
     * applicable: a manipulated URL with someone else's user_id resolves the
     * username only when the searcher actually shares a project with them;
     * otherwise we fall back to the raw token so the result list (which IS
     * permission-bound at SQL level) is the only ground truth.
     */
    private static function resolveAssignedDisplay(string $value, int $userId, Database $db): string
    {
        if ($value === '' || $value === 'u_' . $userId || $value === (string)$userId) {
            return Lang::get('assigned_filter_me');
        }
        if ($value === '__all__') return Lang::get('assigned_filter_anyone');
        if ($value === '_none')   return Lang::get('assigned_filter_none');

        // Admins are not necessarily listed in project_members but still see
        // every user — bypass the membership-overlap permission check for them.
        $isAdmin = Auth::isAdmin();

        if (preg_match('/^u_(\d+)$/', $value, $m)) {
            $sql = $isAdmin
                ? "SELECT u.username FROM users u WHERE u.id = ?"
                : "SELECT u.username FROM users u
                   WHERE u.id = ?
                     AND EXISTS (
                       SELECT 1 FROM project_members pm_t
                       JOIN project_members pm_me ON pm_me.project_id = pm_t.project_id AND pm_me.user_id = ?
                       WHERE pm_t.user_id = u.id
                     )";
            $params = $isAdmin ? [(int)$m[1]] : [(int)$m[1], $userId];
            $row = $db->fetch($sql, $params);
            return $row ? (string)$row['username'] : $value;
        }
        if (preg_match('/^e_(\d+)$/', $value, $m)) {
            $sql = $isAdmin
                ? "SELECT e.name FROM project_externals e
                   JOIN projects p ON p.id = e.project_id
                   WHERE e.id = ? AND p.status != 'archived'"
                : "SELECT e.name FROM project_externals e
                   JOIN projects p ON p.id = e.project_id
                   JOIN project_members pm ON pm.project_id = e.project_id AND pm.user_id = ?
                   WHERE e.id = ? AND p.status != 'archived'";
            $params = $isAdmin ? [(int)$m[1]] : [$userId, (int)$m[1]];
            $row = $db->fetch($sql, $params);
            return $row ? (string)$row['name'] : $value;
        }
        return $value;
    }
}
