async function loadTask(taskId, pushHistory = true) {
    const view = showView('taskView');

    if (!view) return;

    if (pushHistory) {
        const url = new URL(window.location.href);
        url.searchParams.set('page', 'home');
        url.searchParams.set('task', taskId);
        url.searchParams.delete('project');
        url.searchParams.delete('tasks');
        url.searchParams.delete('version');
        url.searchParams.delete('profile');
        url.searchParams.delete('admin');
        window.history.pushState({}, '', url);
    }

    const result = await apiGet('/api/task.php', {
        api: 'TaskInfo',
        task_id: taskId,
        _: Date.now()
    });

    if (!result.success) {
        view.innerHTML = '<div class="alert alert-danger">Task not found.</div>';
        return;
    }

    await renderTask(result.task);
    await loadTaskComments(result.task.id);
    await ensureProjectOpen(result.task.project);
}

async function renderTask(task) {
    currentTask = task;

    const [types, priorities, versions, reporter, assignee] = await Promise.all([
        getTaskTypes(),
        getTaskPriorities(),
        getProjectVersions(task.project),
        getUserInfo(task.reporter),
        task.assignee ? getUserInfo(task.assignee) : null
    ]);

    const type = types.find((item) => String(item.id) === String(task.type)) ?? null;
    const priority = priorities.find((item) => String(item.id) === String(task.priority)) ?? null;
    const fixVersion = versions.find((item) => String(item.id) === String(task.fix_version)) ?? null;

    setEditableText('taskKey', task.id);
    setEditableText('taskTitle', task.title, task.title);
    setEditableText('taskDescription', task.description, task.description, 'No description provided.');
    setText('taskProject', task.project);
    document.getElementById('taskReporter').innerHTML = renderUser(reporter);
    setEditableHtml('taskAssignee', renderUser(assignee), task.assignee ?? '', 'Unassigned');
    setEditableText('taskFixVersion', fixVersion ? fixVersion.name : null, task.fix_version ?? '', 'No fix version');
    setText('taskCreated', task.created_date);
    setText('taskUpdated', task.last_changed);

    setEditableHtml('taskType', renderMetaBadge(type), task.type);
    setEditableHtml('taskPriority', renderMetaBadge(priority), task.priority);
    renderTaskStatus(task);
    renderTaskCustomFields(task.custom_fields ?? []);
}

function renderTaskStatus(task) {
    const slot = document.getElementById('taskStatusSlot');

    if (!slot) return;

    slot.innerHTML = '';

    if (!task.status_state) return;

    const isCurrentAssignee = String(task.assignee ?? '') === String(getCurrentUserId());
    const canTransition = (canEditTasks() || appFlag('isAdmin') || isCurrentAssignee)
        && (task.status_transitions ?? []).length > 0;
    const statusStyle = `--task-status-color: ${escapeHtml(task.status_state.color ?? '#6c757d')}`;

    if (!canTransition) {
        slot.innerHTML = `
            <span class="task-status-badge" style="${statusStyle}">
                ${escapeHtml(task.status_state.name)}
            </span>
        `;
        return;
    }

    slot.innerHTML = `
        <div class="task-status-control">
            <button class="task-status-badge task-status-button" type="button" style="${statusStyle}" id="taskStatusButton">
                <span>${escapeHtml(task.status_state.name)}</span>
                <i class="fa-solid fa-chevron-down"></i>
            </button>
            <div class="task-status-menu" id="taskStatusMenu" hidden>
                ${(task.status_transitions ?? []).map((transition) => `
                    <button type="button" data-task-transition="${escapeHtml(transition.id)}">
                        <span>${escapeHtml(transition.action_name)}</span>
                        <small>${escapeHtml(transition.to_state?.name ?? '')}</small>
                    </button>
                `).join('')}
            </div>
        </div>
    `;
}

function renderTaskCustomFields(fields) {
    const panel = document.getElementById('taskCustomFieldsPanel');
    const list = document.getElementById('taskCustomFieldList');

    if (!panel || !list) return;

    const canEdit = canEditTasks();

    panel.hidden = fields.length === 0;
    list.innerHTML = fields.map((field) => `
        <div class="task-custom-field-row">
            <span>${escapeHtml(field.name)}</span>
            <strong
                class="task-custom-field-value ${field.raw_value ? '' : 'is-empty'} ${canEdit ? '' : 'is-readonly'}"
                ${canEdit ? 'tabindex="0"' : 'aria-disabled="true"'}
                data-custom-field-id="${escapeHtml(field.id)}"
                data-custom-field-type="${escapeHtml(field.type)}"
                data-current-value="${escapeHtml(field.raw_value ?? '')}"
            >${field.raw_value ? escapeHtml(field.raw_value) : 'No value'}</strong>
        </div>
    `).join('');
}

async function loadTaskComments(taskId) {
    const list = document.getElementById('taskCommentList');
    const empty = document.getElementById('taskCommentEmpty');

    if (!list || !empty) return;

    list.innerHTML = '';
    empty.hidden = true;
    currentTaskComments = [];
    setText('taskCommentStatus', '');

    const result = await apiGet('/api/task.php', {
        api: 'ListComments',
        task_id: taskId,
        _: Date.now()
    });

    if (!result.success) {
        list.innerHTML = '<div class="alert alert-danger mb-0">Could not load comments.</div>';
        return;
    }

    currentTaskComments = result.comments ?? [];
    renderTaskComments();
}

function renderTaskComments() {
    const list = document.getElementById('taskCommentList');
    const empty = document.getElementById('taskCommentEmpty');

    if (!list || !empty) return;

    list.innerHTML = '';
    empty.hidden = currentTaskComments.length > 0;

    const commentsByParent = groupCommentsByParent(currentTaskComments);
    const renderComment = (comment, depth = 0) => {
        const user = {
            id: comment.commenter,
            name: comment.commenter_name,
            email: comment.commenter_email,
            picture: comment.commenter_picture
        };
        const article = document.createElement('article');
        const canManageComment = String(comment.commenter) === String(getCurrentUserId());
        const manageActions = canManageComment
            ? `
                <button class="task-comment-action" type="button" data-comment-edit="${escapeHtml(comment.id)}">Edit</button>
                <button class="task-comment-action text-danger" type="button" data-comment-delete="${escapeHtml(comment.id)}">Delete</button>
            `
            : '';
        article.className = `task-comment${comment.response_to ? ' is-reply' : ''}`;
        article.style.setProperty('--comment-depth', String(Math.min(depth, 8)));
        article.dataset.commentId = comment.id;
        article.dataset.commentText = comment.comment;
        article.innerHTML = `
            <div class="task-comment-header">
                ${renderUser(user)}
                <span class="task-comment-id">#${escapeHtml(comment.id)}</span>
            </div>
            <div class="task-comment-body">${escapeHtml(comment.comment)}</div>
            <div class="task-comment-action-row">
                <button class="task-comment-action" type="button" data-comment-reply="${escapeHtml(comment.id)}">Reply</button>
                ${manageActions}
            </div>
            <div class="task-comment-reply-slot"></div>
            <div class="task-comment-edit-slot"></div>
        `;

        list.appendChild(article);

        const children = commentsByParent.get(Number(comment.id)) ?? [];
        children.forEach((child) => renderComment(child, depth + 1));
    };

    const rootComments = commentsByParent.get(null) ?? [];
    rootComments.forEach((comment) => renderComment(comment));
}

function groupCommentsByParent(comments) {
    const knownIds = new Set(comments.map((comment) => Number(comment.id)));
    const groups = new Map([[null, []]]);

    [...comments]
        .sort((first, second) => Number(first.id) - Number(second.id))
        .forEach((comment) => {
            const parentId = comment.response_to && knownIds.has(Number(comment.response_to))
                ? Number(comment.response_to)
                : null;

            if (!groups.has(parentId)) {
                groups.set(parentId, []);
            }

            groups.get(parentId).push(comment);
        });

    return groups;
}

async function submitTaskComment(comment, responseTo = null) {
    if (!currentTask) return;

    const result = await apiPost('/api/task.php', {
        api: 'CreateComment',
        task_id: currentTask.id
    }, {
        comment,
        response_to: responseTo
    });

    if (!result.success) {
        throw new Error(result.error || 'Could not save comment.');
    }

    await loadTaskComments(currentTask.id);
}

async function updateTaskComment(commentId, comment) {
    if (!currentTask) return;

    const result = await apiPost('/api/task.php', {
        api: 'EditComment',
        task_id: currentTask.id,
        comment_id: commentId
    }, {
        comment
    });

    if (!result.success) {
        throw new Error(result.error || 'Could not update comment.');
    }

    await loadTaskComments(currentTask.id);
}

async function deleteTaskComment(commentId) {
    if (!currentTask) return;

    const result = await apiPost('/api/task.php', {
        api: 'DeleteComment',
        task_id: currentTask.id,
        comment_id: commentId
    });

    if (!result.success) {
        throw new Error(result.error || 'Could not delete comment.');
    }

    await loadTaskComments(currentTask.id);
}

async function openTaskCreatePopup() {
    const form = document.getElementById('createTaskForm');

    if (!form) return;

    await populateTaskOptionSelects();
    await populateTaskProjectSelect();
    await populateTaskRelationSelects();

    resetTaskPopup();

    openPopup('createTask');
}

async function openTaskEditPopup() {
    if (!currentTask) return;

    const form = document.getElementById('createTaskForm');

    if (!form) return;

    await populateTaskOptionSelects();
    await populateTaskProjectSelect(currentTask.project);
    await populateTaskRelationSelects(currentTask.project, {
        assignee: currentTask.assignee ?? '',
        fix_version: currentTask.fix_version ?? ''
    });

    form.reset();
    form.dataset.mode = 'edit';
    document.getElementById('taskPopupTitle').textContent = `Edit ${currentTask.id}`;
    document.getElementById('taskPopupSubmit').textContent = 'Update Task';
    document.getElementById('taskFormTaskId').value = currentTask.id;
    document.getElementById('taskFormProjectId').value = currentTask.project;
    document.getElementById('taskFormProjectId').disabled = true;
    document.getElementById('taskFormTitle').value = currentTask.title ?? '';
    document.getElementById('taskFormDescription').value = currentTask.description ?? '';
    document.getElementById('createTaskType').value = currentTask.type ?? '';
    document.getElementById('createTaskPriority').value = currentTask.priority ?? '';
    document.getElementById('taskFormFixVersion').value = currentTask.fix_version ?? '';
    document.getElementById('taskFormAssignee').value = currentTask.assignee ?? '';

    openPopup('createTask');
}

function initTaskInlineEditing() {
    if (!canEditTasks()) return;

    document.querySelectorAll('.task-editable').forEach((element) => {
        if (element.dataset.inlineReady === 'true') return;

        element.dataset.inlineReady = 'true';
        element.title = 'Click to edit';

        element.addEventListener('click', () => openTaskInlineEditor(element));
        element.addEventListener('keydown', (event) => {
            if (event.key === 'Enter' || event.key === ' ') {
                event.preventDefault();
                openTaskInlineEditor(element);
            }
        });
    });
}

async function openTaskInlineEditor(element) {
    if (!canEditTasks() || !currentTask || element.querySelector('.task-inline-form')) return;

    const field = element.dataset.taskField;
    const currentValue = element.dataset.currentValue ?? '';
    const originalHtml = element.innerHTML;
    const originalClasses = Array.from(element.classList);

    element.classList.remove('is-empty');
    element.innerHTML = '';

    const form = document.createElement('form');
    form.className = 'task-inline-form';

    const input = await createTaskInlineInput(field, currentValue);
    form.appendChild(input);

    const actions = document.createElement('div');
    actions.className = 'task-inline-actions';
    actions.innerHTML = `
        <button class="btn btn-sm btn-primary" type="submit">
            <i class="fa-solid fa-check"></i>
        </button>
        <button class="btn btn-sm btn-outline-secondary" type="button" data-inline-cancel>
            <i class="fa-solid fa-xmark"></i>
        </button>
    `;

    if (input.tagName !== 'SELECT') {
        form.appendChild(actions);
    }

    element.appendChild(form);
    input.focus();

    if (input.select && input.tagName !== 'SELECT') {
        input.select();
    }

    let editorClosed = false;

    const restore = () => {
        if (editorClosed) return;

        editorClosed = true;
        element.innerHTML = originalHtml;
        element.className = originalClasses.join(' ');
    };

    let saving = false;

    const save = async () => {
        if (saving) return;

        const nextValue = input.value;

        if (String(nextValue) === String(currentValue)) {
            restore();
            return;
        }

        saving = true;
        element.classList.add('is-saving');

        const result = await apiPost('/api/task.php', {
            api: 'Edit',
            task_id: currentTask.id
        }, {
            [field]: nextValue
        });

        if (!result.success) {
            alert(result.error || 'Could not update task.');
            saving = false;
            restore();
            return;
        }

        editorClosed = true;
        element.classList.remove('is-saving');
        currentTask = {
            ...currentTask,
            [field]: nextValue === '' ? null : nextValue
        };

        if (field === 'type') {
            await loadTask(currentTask.id, false);
            return;
        }

        await renderTask(currentTask);
    };

    form.addEventListener('submit', async (event) => {
        event.preventDefault();
        await save();
    });

    form.querySelector('[data-inline-cancel]')?.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        restore();
    });

    input.addEventListener('keydown', async (event) => {
        if (event.key === 'Escape') {
            event.preventDefault();
            restore();
        }

        if (event.key === 'Enter' && input.tagName === 'TEXTAREA' && (event.ctrlKey || event.metaKey)) {
            event.preventDefault();
            await save();
        }
    });

    if (input.tagName === 'SELECT') {
        input.addEventListener('change', save);
        input.addEventListener('blur', () => {
            window.setTimeout(() => {
                if (editorClosed) return;
                if (element.classList.contains('is-saving')) return;
                if (element.contains(document.activeElement)) return;
                restore();
            }, 120);
        });
    }
}

async function createTaskInlineInput(field, currentValue) {
    if (field === 'description') {
        const textarea = document.createElement('textarea');
        textarea.className = 'form-control task-inline-input';
        textarea.rows = 5;
        textarea.value = currentValue;
        return textarea;
    }

    if (field === 'type') {
        const select = document.createElement('select');
        select.className = 'form-select task-inline-select';
        populateSelect(select, await getTaskTypes(), {
            includeEmpty: false,
            selectedValue: currentValue
        });
        return select;
    }

    if (field === 'priority') {
        const select = document.createElement('select');
        select.className = 'form-select task-inline-select';
        populateSelect(select, await getTaskPriorities(), {
            includeEmpty: false,
            selectedValue: currentValue
        });
        return select;
    }

    if (field === 'fix_version') {
        const select = document.createElement('select');
        select.className = 'form-select task-inline-select';
        populateSelect(select, await getProjectVersions(currentTask.project), {
            placeholder: 'No fix version',
            selectedValue: currentValue
        });
        return select;
    }

    if (field === 'assignee') {
        const select = document.createElement('select');
        select.className = 'form-select task-inline-select';
        populateSelect(select, await getUsers(), {
            placeholder: 'Unassigned',
            selectedValue: currentValue
        });
        return select;
    }

    const input = document.createElement('input');
    input.className = 'form-control task-inline-input';
    input.type = 'text';
    input.value = currentValue;
    input.required = field === 'title';
    return input;
}

function createCustomFieldInput(type, currentValue) {
    if (type === 'boolean') {
        const select = document.createElement('select');
        select.className = 'form-select task-inline-select';
        [
            { id: '', name: 'No value' },
            { id: 'true', name: 'True' },
            { id: 'false', name: 'False' }
        ].forEach((item) => {
            const option = document.createElement('option');
            option.value = item.id;
            option.textContent = item.name;
            option.selected = String(item.id) === String(currentValue);
            select.appendChild(option);
        });
        return select;
    }

    if (type === 'text' || type === 'json') {
        const textarea = document.createElement('textarea');
        textarea.className = 'form-control task-inline-input';
        textarea.rows = type === 'json' ? 4 : 3;
        textarea.value = currentValue;
        return textarea;
    }

    const input = document.createElement('input');
    input.className = 'form-control task-inline-input';
    input.type = type === 'date' ? 'date' : 'text';
    input.inputMode = ['int', 'float'].includes(type) ? 'decimal' : '';
    input.value = currentValue;
    return input;
}

async function openCustomFieldInlineEditor(element) {
    if (!canEditTasks() || !currentTask || element.querySelector('.task-inline-form')) return;

    const fieldId = element.dataset.customFieldId;
    const fieldType = element.dataset.customFieldType ?? 'string';
    const currentValue = element.dataset.currentValue ?? '';
    const originalHtml = element.innerHTML;
    const originalClasses = Array.from(element.classList);

    element.classList.remove('is-empty');
    element.innerHTML = '';

    const form = document.createElement('form');
    form.className = 'task-inline-form';

    const input = createCustomFieldInput(fieldType, currentValue);
    form.appendChild(input);

    const actions = document.createElement('div');
    actions.className = 'task-inline-actions';
    actions.innerHTML = `
        <button class="btn btn-sm btn-primary" type="submit">
            <i class="fa-solid fa-check"></i>
        </button>
        <button class="btn btn-sm btn-outline-secondary" type="button" data-inline-cancel>
            <i class="fa-solid fa-xmark"></i>
        </button>
    `;
    form.appendChild(actions);

    element.appendChild(form);
    input.focus();

    if (input.select && input.tagName !== 'SELECT') {
        input.select();
    }

    let closed = false;
    const restore = () => {
        if (closed) return;

        closed = true;
        element.innerHTML = originalHtml;
        element.className = originalClasses.join(' ');
    };

    const save = async () => {
        const nextValue = input.value;

        if (String(nextValue) === String(currentValue)) {
            restore();
            return;
        }

        element.classList.add('is-saving');

        const result = await apiPost('/api/task.php', {
            api: 'SetCustomFieldValue',
            task_id: currentTask.id
        }, {
            field_id: fieldId,
            value: nextValue
        });

        if (!result.success) {
            alert(result.error || 'Could not update custom field.');
            restore();
            return;
        }

        closed = true;
        await loadTask(currentTask.id, false);
    };

    form.addEventListener('submit', async (event) => {
        event.preventDefault();
        await save();
    });

    form.querySelector('[data-inline-cancel]')?.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        restore();
    });

    input.addEventListener('keydown', async (event) => {
        if (event.key === 'Escape') {
            event.preventDefault();
            restore();
        }

        if (event.key === 'Enter' && input.tagName !== 'TEXTAREA') {
            event.preventDefault();
            await save();
        }

        if (event.key === 'Enter' && input.tagName === 'TEXTAREA' && (event.ctrlKey || event.metaKey)) {
            event.preventDefault();
            await save();
        }
    });
}

async function transitionCurrentTask(transitionId) {
    if (!currentTask || !transitionId) return;

    const result = await apiPost('/api/task.php', {
        api: 'TransitionStatus',
        task_id: currentTask.id
    }, {
        transition_id: transitionId
    });

    if (!result.success) {
        alert(result.error || 'Could not transition task.');
        return;
    }

    await loadTask(currentTask.id, false);
}

