async function loadProjectTree() {
    const tree = document.getElementById('projectTree');

    if (!tree) return;

    tree.innerHTML = '<div class="kiln-tree-muted">Loading projects...</div>';

    const result = await apiGet('/api/project.php', {
        api: 'ListProjects'
    });

    if (!result.success) {
        tree.innerHTML = '<div class="text-danger">Could not load projects.</div>';
        return;
    }

    if (!result.projects.length) {
        tree.innerHTML = '<div class="kiln-tree-muted">No projects yet.</div>';
        return;
    }

    tree.innerHTML = '';
    taskLookupCache.projectBlocksById.clear();

    for (const projectId of result.projects) {
        const project = await getProjectInfo(projectId);
        const projectBlock = document.createElement('div');
        projectBlock.dataset.projectId = projectId;

        const projectButton = document.createElement('button');
        projectButton.className = 'kiln-tree-project';
        projectButton.innerHTML = `
            <i class="fa-solid fa-caret-right me-1" data-project-caret></i>
            <span>${escapeHtml(project?.name ?? projectId)}</span>
        `;
        projectButton.addEventListener('click', (event) => {
            if (event.target.closest('[data-project-caret]')) {
                toggleProject(projectId, projectBlock);
                return;
            }

            loadProject(projectId);

            if (!projectBlock.querySelector('.kiln-tree-group')) {
                toggleProject(projectId, projectBlock, true);
            }
        });

        projectBlock.appendChild(projectButton);
        tree.appendChild(projectBlock);
        taskLookupCache.projectBlocksById.set(projectId, projectBlock);
    }

    if (currentTask?.project) {
        const projectBlock = taskLookupCache.projectBlocksById.get(currentTask.project);

        if (projectBlock && !projectBlock.querySelector('.kiln-tree-group')) {
            await toggleProject(currentTask.project, projectBlock, true);
        }
    }
}

async function getProjectInfo(projectId) {
    if (taskLookupCache.projectsById.has(projectId)) {
        return taskLookupCache.projectsById.get(projectId);
    }

    const result = await apiGet('/api/project.php', {
        api: 'ProjectInfo',
        project_id: projectId
    });

    const project = result.success ? result.project : null;
    taskLookupCache.projectsById.set(projectId, project);
    return project;
}

async function ensureProjectOpen(projectId) {
    await projectTreePromise;

    const projectBlock = taskLookupCache.projectBlocksById.get(projectId);

    if (!projectBlock || projectBlock.querySelector('.kiln-tree-group')) {
        return;
    }

    await toggleProject(projectId, projectBlock, true);
}

async function toggleProject(projectId, projectBlock, forceOpen = false) {
    let group = projectBlock.querySelector('.kiln-tree-group');

    if (group) {
        if (forceOpen) return;

        group.remove();
        setProjectCaret(projectBlock, false);
        return;
    }

    setProjectCaret(projectBlock, true);

    group = document.createElement('div');
    group.className = 'kiln-tree-group';
    group.innerHTML = '<div class="kiln-tree-muted">Loading...</div>';
    projectBlock.appendChild(group);

    const versions = await apiGet('/api/version.php', {
        api: 'ListVersions',
        project_id: projectId
    });

    group.innerHTML = '';

    const versionGroup = document.createElement('div');
    versionGroup.className = 'kiln-tree-version-group';
    group.appendChild(versionGroup);

    const versionLabel = document.createElement('div');
    versionLabel.className = 'kiln-tree-label';
    versionLabel.textContent = 'Versions';
    versionGroup.appendChild(versionLabel);

    if (document.querySelector('.kiln-app')?.dataset.canCreateVersions === '1') {
        const createVersion = document.createElement('button');
        createVersion.className = 'kiln-tree-version';
        createVersion.innerHTML = '<i class="fa-solid fa-plus me-1"></i>Create new version';
        createVersion.addEventListener('click', () => openVersionCreatePopup(projectId));
        versionGroup.appendChild(createVersion);
    }

    if (versions.success && versions.versions.length) {
        for (const versionId of versions.versions) {
            const versionInfo = await apiGet('/api/version.php', {
                api: 'VersionInfo',
                version_id: versionId
            });

            const versionButton = document.createElement('button');
            versionButton.className = 'kiln-tree-version';

            if (versionInfo.success && versionInfo.version) {
                versionButton.textContent = versionInfo.version.name;
            } else {
                versionButton.textContent = `Version ${versionId}`;
            }

            versionButton.addEventListener('click', () => loadVersion(versionId));
            versionGroup.appendChild(versionButton);
        }
    }

    const showTasksButton = document.createElement('button');
    showTasksButton.className = 'kiln-tree-version';
    showTasksButton.innerHTML = '<i class="fa-solid fa-list-check me-1"></i>Show all tasks';
    showTasksButton.addEventListener('click', (event) => {
        event.stopPropagation();
        loadProjectTasks(projectId);
    });

    group.appendChild(showTasksButton);
}

function setProjectCaret(projectBlock, isOpen) {
    const caret = projectBlock.querySelector('[data-project-caret]');

    if (!caret) return;

    caret.classList.toggle('fa-caret-right', !isOpen);
    caret.classList.toggle('fa-caret-down', isOpen);
}

async function loadProject(projectId, pushHistory = true) {
    const view = showView('projectView');

    if (!view) return;

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

    const result = await apiGet('/api/project.php', {
        api: 'ProjectInfo',
        project_id: projectId,
        _: Date.now()
    });

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

    await renderProject(result.project);
    await ensureProjectOpen(result.project.id);
}

async function renderProject(project) {
    currentProject = project;
    taskLookupCache.projectsById.set(project.id, project);

    const owner = project.owner ? await getUserInfo(project.owner) : null;

    setVersionEditableText('projectKey', project.id);
    setVersionEditableText('projectName', project.name, project.name);
    setVersionEditableText('projectInlineName', project.name, project.name);
    setText('projectId', project.id);
    setEditableHtml('projectOwner', renderUser(owner), project.owner ?? '', 'No owner');
    setText('projectCreated', project.created_date || '-');
}

async function populateProjectOwnerSelect(selectedOwner = '') {
    populateSelect(document.getElementById('projectFormOwner'), await getUsers(), {
        includeEmpty: false,
        selectedValue: selectedOwner
    });
}

function resetProjectPopup() {
    const form = document.getElementById('createProjectForm');

    if (!form) return;

    form.reset();
    form.dataset.mode = 'create';
    document.getElementById('projectPopupTitle').textContent = 'Create Project';
    document.getElementById('projectPopupSubmit').textContent = 'Create Project';
    document.getElementById('projectFormId').disabled = false;
    document.getElementById('projectFormId').value = '';
    document.getElementById('projectFormName').value = '';
}

async function openProjectCreatePopup() {
    resetProjectPopup();
    await populateProjectOwnerSelect(getCurrentUserId());
    openPopup('createProject');
}

async function openProjectEditPopup() {
    if (!currentProject) return;

    resetProjectPopup();

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

    if (!form) return;

    form.dataset.mode = 'edit';
    document.getElementById('projectPopupTitle').textContent = `Edit ${currentProject.id}`;
    document.getElementById('projectPopupSubmit').textContent = 'Update Project';
    document.getElementById('projectFormId').value = currentProject.id;
    document.getElementById('projectFormId').disabled = true;
    document.getElementById('projectFormName').value = currentProject.name ?? '';
    await populateProjectOwnerSelect(currentProject.owner ?? '');

    openPopup('createProject');
}

function initProjectInlineEditing() {
    if (!canEditProjects()) return;

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

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

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

async function openProjectInlineEditor(element) {
    if (!canEditProjects() || !currentProject || element.querySelector('.version-inline-form')) return;

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

    if (!['name', 'owner'].includes(field)) return;

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

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

    let input;

    if (field === 'owner') {
        input = document.createElement('select');
        input.className = 'form-select version-inline-input';
        populateSelect(input, await getUsers(), {
            includeEmpty: false,
            selectedValue: currentValue
        });
    } else {
        input = document.createElement('input');
        input.className = 'form-control version-inline-input';
        input.type = 'text';
        input.required = true;
        input.maxLength = 128;
        input.value = currentValue;
    }

    form.appendChild(input);

    const actions = document.createElement('div');
    actions.className = 'version-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-project-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 = field === 'name' ? input.value.trim() : input.value;

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

        const result = await apiPost('/api/project.php', {
            api: 'Edit',
            project_id: currentProject.id
        }, {
            [field]: nextValue
        });

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

        taskLookupCache.projectsById.delete(currentProject.id);
        closed = true;
        await loadProject(currentProject.id, false);
        projectTreePromise = loadProjectTree();
    };

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

    if (input.tagName === 'SELECT') {
        input.addEventListener('change', save);
    }

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

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

