async function loadWorkflowEditor() {
    const result = await apiGet('/api/workflow.php', {
        api: 'WorkflowData',
        _: Date.now()
    });

    if (!result.success) {
        document.getElementById('workflowGraph').innerHTML = '<div class="alert alert-danger m-3">Could not load workflow data.</div>';
        return;
    }

    workflowData = result.workflow;
    if (!selectedWorkflowTaskType && workflowData.task_types.length) {
        selectedWorkflowTaskType = String(workflowData.task_types[0].id);
    }

    if (selectedWorkflowTaskType && !workflowData.task_types.some((type) => String(type.id) === String(selectedWorkflowTaskType))) {
        selectedWorkflowTaskType = workflowData.task_types.length ? String(workflowData.task_types[0].id) : null;
    }

    renderWorkflowEditor();
}

function renderWorkflowEditor() {
    if (!workflowData) return;

    populateWorkflowSelects();
    renderWorkflowLists();
    renderWorkflowGraph();
}

function populateWorkflowSelects() {
    const stateOptions = workflowData.states.map((state) => ({
        id: state.id,
        name: state.name
    }));

    populateSelect(document.getElementById('workflowTransitionFrom'), stateOptions, {
        placeholder: 'From state'
    });
    populateSelect(document.getElementById('workflowTransitionTo'), stateOptions, {
        placeholder: 'To state'
    });
    populateSelect(document.getElementById('workflowAssignmentState'), stateOptions, {
        placeholder: 'State'
    });
    populateSelect(document.getElementById('workflowTaskTypeSelect'), workflowData.task_types, {
        placeholder: 'Task type',
        selectedValue: selectedWorkflowTaskType ?? ''
    });

    const assignedStates = getSelectedWorkflowAssignments()
        .map((assignment) => workflowData.states.find((state) => Number(state.id) === Number(assignment.state)))
        .filter(Boolean);
    const selectedType = getSelectedWorkflowTaskType();

    populateSelect(document.getElementById('workflowDefaultStateSelect'), assignedStates, {
        placeholder: 'No default state',
        selectedValue: selectedType?.default_state ?? ''
    });
}

function renderWorkflowLists() {
    const statesById = new Map(workflowData.states.map((state) => [Number(state.id), state]));
    const selectedAssignments = getSelectedWorkflowAssignments();

    const stateList = document.getElementById('workflowStateList');
    stateList.innerHTML = workflowData.states.map((state) => `
        <div class="workflow-list-item">
            <div class="workflow-list-main">
                <span class="workflow-color-dot" style="background: ${escapeHtml(state.color)}"></span>
                <span>${escapeHtml(state.name)}</span>
            </div>
            <button class="btn btn-sm btn-outline-danger" type="button" data-workflow-delete-state="${escapeHtml(state.id)}">
                <i class="fa-solid fa-trash-can"></i>
            </button>
        </div>
    `).join('') || '<div class="text-secondary">No states yet.</div>';

    const transitionList = document.getElementById('workflowTransitionList');
    transitionList.innerHTML = workflowData.transitions.map((transition) => `
        <div class="workflow-list-item">
            <div class="workflow-list-main">
                <span>${escapeHtml(statesById.get(Number(transition.from_id))?.name ?? transition.from_id)}</span>
                <i class="fa-solid fa-arrow-right"></i>
                <span>${escapeHtml(statesById.get(Number(transition.to_id))?.name ?? transition.to_id)}</span>
                <span class="text-secondary">${escapeHtml(transition.action_name)}</span>
            </div>
            <button class="btn btn-sm btn-outline-danger" type="button" data-workflow-delete-transition="${escapeHtml(transition.id)}">
                <i class="fa-solid fa-trash-can"></i>
            </button>
        </div>
    `).join('') || '<div class="text-secondary">No transitions yet.</div>';

    const assignmentList = document.getElementById('workflowAssignmentList');
    assignmentList.innerHTML = selectedAssignments.map((assignment) => `
        <div class="workflow-list-item">
            <div class="workflow-list-main">
                <span>${escapeHtml(statesById.get(Number(assignment.state))?.name ?? assignment.state)}</span>
            </div>
            <button class="btn btn-sm btn-outline-danger" type="button" data-workflow-remove-assignment="${escapeHtml(assignment.id)}">
                <i class="fa-solid fa-trash-can"></i>
            </button>
        </div>
    `).join('') || '<div class="text-secondary">No assigned states yet.</div>';
}

function getSelectedWorkflowTaskType() {
    return workflowData?.task_types.find((type) => String(type.id) === String(selectedWorkflowTaskType)) ?? null;
}

function getSelectedWorkflowAssignments() {
    if (!selectedWorkflowTaskType) return [];

    return workflowData.assignments.filter((assignment) => String(assignment.task_type) === String(selectedWorkflowTaskType));
}

function renderWorkflowGraph() {
    const graph = document.getElementById('workflowGraph');
    const empty = document.getElementById('workflowGraphEmpty');

    if (!graph || !empty) return;

    const assignedStateIds = new Set(getSelectedWorkflowAssignments().map((assignment) => Number(assignment.state)));
    const visibleStates = workflowData.states.filter((state) => assignedStateIds.has(Number(state.id)));
    const visibleTransitions = workflowData.transitions.filter((transition) => (
        assignedStateIds.has(Number(transition.from_id))
        && assignedStateIds.has(Number(transition.to_id))
    ));

    if (!visibleStates.length) {
        if (workflowGraph) {
            workflowGraph.destroy();
            workflowGraph = null;
        }

        graph.innerHTML = '';
        empty.hidden = false;
        empty.textContent = selectedWorkflowTaskType
            ? 'Assign states to this task type to render its workflow graph.'
            : 'Select a task type to render its workflow graph.';
        return;
    }

    empty.hidden = true;

    if (typeof cytoscape !== 'function') {
        graph.innerHTML = '<div class="alert alert-warning m-3">Cytoscape.js could not be loaded.</div>';
        return;
    }

    const elements = [
        ...visibleStates.map((state) => ({
            data: {
                id: `state-${state.id}`,
                label: state.name,
                color: state.color
            }
        })),
        ...visibleTransitions.map((transition) => ({
            data: {
                id: `transition-${transition.id}`,
                source: `state-${transition.from_id}`,
                target: `state-${transition.to_id}`,
                label: transition.action_name
            }
        }))
    ];
    const styles = getComputedStyle(document.documentElement);
    const borderColor = styles.getPropertyValue('--bs-border-color').trim() || '#3a3f46';
    const bodyColor = styles.getPropertyValue('--bs-body-color').trim() || '#f3f4f6';
    const secondaryColor = styles.getPropertyValue('--bs-secondary-color').trim() || '#aeb7c4';

    if (workflowGraph) {
        workflowGraph.destroy();
    }

    workflowGraph = cytoscape({
        container: graph,
        elements,
        style: [
            {
                selector: 'node',
                style: {
                    'background-color': 'data(color)',
                    'border-color': borderColor,
                    'border-width': 1,
                    'color': bodyColor,
                    'font-size': 13,
                    'font-weight': 700,
                    'label': 'data(label)',
                    'text-valign': 'center',
                    'text-halign': 'center',
                    'text-wrap': 'wrap',
                    'text-max-width': 90,
                    'width': 86,
                    'height': 42,
                    'shape': 'round-rectangle'
                }
            },
            {
                selector: 'edge',
                style: {
                    'curve-style': 'bezier',
                    'target-arrow-shape': 'triangle',
                    'line-color': secondaryColor,
                    'target-arrow-color': secondaryColor,
                    'color': secondaryColor,
                    'font-size': 11,
                    'label': 'data(label)',
                    'text-rotation': 'autorotate',
                    'text-margin-y': -8,
                    'width': 2
                }
            }
        ],
        layout: {
            name: 'breadthfirst',
            directed: true,
            padding: 36,
            spacingFactor: 1.25
        }
    });
}

async function saveWorkflowAction(api, body = {}, params = {}) {
    const result = await apiPost('/api/workflow.php', {
        api,
        ...params
    }, body);

    if (!result.success) {
        alert(result.error || 'Could not save workflow change.');
        return false;
    }

    await loadWorkflowEditor();
    return true;
}

async function loadAdminOptions() {
    let result = null;

    try {
        result = await apiGet('/api/admin_options.php', {
            api: 'OptionsData',
            _: Date.now()
        });
    } catch (error) {
        result = {
            success: false,
            error: 'Could not load admin options.'
        };
    }

    if (!result.success) {
        const typeEditor = document.getElementById('adminSelectedTypeEditor');
        const priorityList = document.getElementById('adminPriorityList');
        const userRightsList = document.getElementById('adminUserRightsList');

        if (typeEditor) {
            typeEditor.innerHTML = '<div class="text-danger">Could not load task types.</div>';
        }

        if (priorityList) {
            priorityList.innerHTML = '<div class="text-danger">Could not load priorities.</div>';
        }

        if (userRightsList) {
            userRightsList.innerHTML = '<div class="text-danger">Could not load users.</div>';
        }

        return;
    }

    adminOptionsData = {
        types: result.options?.types ?? [],
        priorities: result.options?.priorities ?? [],
        custom_fields: result.options?.custom_fields ?? [],
        users: result.options?.users ?? [],
        rights: result.options?.rights ?? [],
        user_rights: result.options?.user_rights ?? [],
        projects: result.options?.projects ?? [],
        user_access: result.options?.user_access ?? []
    };
    if (!selectedAdminTaskType && adminOptionsData.types.length) {
        selectedAdminTaskType = String(adminOptionsData.types[0].id);
    }

    if (selectedAdminTaskType && !adminOptionsData.types.some((type) => String(type.id) === String(selectedAdminTaskType))) {
        selectedAdminTaskType = adminOptionsData.types.length ? String(adminOptionsData.types[0].id) : null;
    }

    if (!selectedAdminUser && adminOptionsData.users.length) {
        selectedAdminUser = String(adminOptionsData.users[0].id);
    }

    if (selectedAdminUser && !adminOptionsData.users.some((user) => String(user.id) === String(selectedAdminUser))) {
        selectedAdminUser = adminOptionsData.users.length ? String(adminOptionsData.users[0].id) : null;
    }

    renderAdminOptions();
}

function renderAdminOptions() {
    if (!adminOptionsData) return;

    const types = adminOptionsData.types ?? [];
    const priorities = adminOptionsData.priorities ?? [];

    populateSelect(document.getElementById('adminTaskTypeSelect'), types, {
        placeholder: 'Select task type',
        selectedValue: selectedAdminTaskType ?? ''
    });
    renderSelectedAdminTaskType();
    renderAdminCustomFields();
    renderAdminOptionList('priority', priorities, document.getElementById('adminPriorityList'));
    renderAdminUsers();
}

function getSelectedAdminTaskType() {
    return adminOptionsData?.types.find((type) => String(type.id) === String(selectedAdminTaskType)) ?? null;
}

function renderSelectedAdminTaskType() {
    const container = document.getElementById('adminSelectedTypeEditor');

    if (!container) return;

    const type = getSelectedAdminTaskType();

    if (!type) {
        container.innerHTML = '<div class="text-secondary">Create or select a task type first.</div>';
        return;
    }

    container.innerHTML = `
        <form class="admin-option-item admin-selected-option" data-admin-option-edit="type" data-option-id="${escapeHtml(type.id)}">
            <div class="admin-option-icon">
                ${type.icon
                    ? `<img src="${escapeHtml(type.icon)}" alt="">`
                    : '<i class="fa-solid fa-tag"></i>'}
            </div>
            <input class="form-control form-control-sm" name="name" value="${escapeHtml(type.name)}" required maxlength="20">
            <input class="form-control form-control-sm" type="file" name="logo" accept=".svg,image/svg+xml" aria-label="Logo">
            <label class="admin-option-remove-logo">
                <input type="checkbox" name="remove_logo" value="1">
                <span>Remove logo</span>
            </label>
            <div class="admin-option-actions">
                <button class="btn btn-sm btn-primary" type="submit" title="Save">
                    <i class="fa-solid fa-check"></i>
                </button>
                <button class="btn btn-sm btn-outline-danger" type="button" data-admin-option-delete="type" data-option-id="${escapeHtml(type.id)}" title="Delete">
                    <i class="fa-solid fa-trash-can"></i>
                </button>
            </div>
        </form>
    `;
}

function renderAdminCustomFields() {
    const container = document.getElementById('adminCustomFieldList');

    if (!container) return;

    if (!selectedAdminTaskType) {
        container.innerHTML = '<div class="text-secondary">Select a task type first.</div>';
        return;
    }

    const fields = (adminOptionsData.custom_fields ?? [])
        .filter((field) => String(field.task_type) === String(selectedAdminTaskType));

    container.innerHTML = fields.map((field) => `
        <form class="admin-custom-field-item" data-admin-custom-field-edit="${escapeHtml(field.id)}">
            <input class="form-control form-control-sm" name="name" value="${escapeHtml(field.name)}" required maxlength="128">
            <select class="form-select form-select-sm" name="type" required>
                ${customFieldTypes.map((type) => `
                    <option value="${escapeHtml(type.id)}" ${String(type.id) === String(field.type) ? 'selected' : ''}>${escapeHtml(type.name)}</option>
                `).join('')}
            </select>
            <input class="form-control form-control-sm" name="value" value="${escapeHtml(field.raw_value ?? '')}">
            <div class="admin-option-actions">
                <button class="btn btn-sm btn-primary" type="submit" title="Save">
                    <i class="fa-solid fa-check"></i>
                </button>
                <button class="btn btn-sm btn-outline-danger" type="button" data-admin-custom-field-delete="${escapeHtml(field.id)}" title="Delete">
                    <i class="fa-solid fa-trash-can"></i>
                </button>
            </div>
        </form>
    `).join('') || '<div class="text-secondary">No custom fields yet.</div>';
}

function renderAdminOptionList(kind, options, container) {
    if (!container) return;

    container.innerHTML = options.map((option) => `
        <form class="admin-option-item" data-admin-option-edit="${escapeHtml(kind)}" data-option-id="${escapeHtml(option.id)}">
            <div class="admin-option-icon">
                ${option.icon
                    ? `<img src="${escapeHtml(option.icon)}" alt="">`
                    : '<i class="fa-solid fa-tag"></i>'}
            </div>
            <input class="form-control form-control-sm" name="name" value="${escapeHtml(option.name)}" required maxlength="128">
            <input class="form-control form-control-sm" type="file" name="logo" accept=".svg,image/svg+xml" aria-label="Logo">
            <label class="admin-option-remove-logo">
                <input type="checkbox" name="remove_logo" value="1">
                <span>Remove logo</span>
            </label>
            <div class="admin-option-actions">
                <button class="btn btn-sm btn-primary" type="submit" title="Save">
                    <i class="fa-solid fa-check"></i>
                </button>
                <button class="btn btn-sm btn-outline-danger" type="button" data-admin-option-delete="${escapeHtml(kind)}" data-option-id="${escapeHtml(option.id)}" title="Delete">
                    <i class="fa-solid fa-trash-can"></i>
                </button>
            </div>
        </form>
    `).join('') || '<div class="text-secondary">No entries yet.</div>';
}

function getSelectedAdminUser() {
    return adminOptionsData?.users.find((user) => String(user.id) === String(selectedAdminUser)) ?? null;
}

function userHasAdminRight(userId, rightId) {
    return (adminOptionsData?.user_rights ?? []).some((entry) => (
        String(entry.user_id) === String(userId)
        && String(entry.right_id) === String(rightId)
    ));
}

function getAdminUserAccess(userId) {
    return (adminOptionsData?.user_access ?? []).filter((entry) => (
        String(entry.user_id) === String(userId)
    ));
}

function getAdminProject(projectId) {
    return (adminOptionsData?.projects ?? []).find((project) => (
        String(project.id) === String(projectId)
    )) ?? null;
}

function renderAdminUsers() {
    const select = document.getElementById('adminUserSelect');
    const container = document.getElementById('adminUserRightsList');

    if (!select || !container) return;

    const users = adminOptionsData.users ?? [];
    const rights = adminOptionsData.rights ?? [];
    const projects = adminOptionsData.projects ?? [];
    const selectedUser = getSelectedAdminUser();

    populateSelect(select, users, {
        placeholder: 'Select user',
        selectedValue: selectedAdminUser ?? ''
    });

    if (!selectedUser) {
        container.innerHTML = '<div class="text-secondary">Select a user first.</div>';
        return;
    }

    if (!rights.length) {
        container.innerHTML = '<div class="text-secondary">No rights configured yet.</div>';
        return;
    }

    const explicitAccess = getAdminUserAccess(selectedUser.id);
    const explicitProjectIds = new Set(explicitAccess.map((entry) => String(entry.project_id)));
    const ownerProjects = projects.filter((project) => String(project.owner) === String(selectedUser.id));
    const ownerProjectIds = new Set(ownerProjects.map((project) => String(project.id)));
    const addableProjects = projects.filter((project) => (
        !explicitProjectIds.has(String(project.id))
        && !ownerProjectIds.has(String(project.id))
    ));

    container.innerHTML = `
        <div class="admin-user-summary">
            ${renderUser(selectedUser)}
        </div>

        <div class="admin-user-section">
            <h4>Rights</h4>
            <div class="admin-right-list">
                ${rights.map((right) => {
                    const checked = userHasAdminRight(selectedUser.id, right.id);
                    const isSelfAdminRight = appFlag('isAdmin')
                        && String(selectedUser.id) === String(getCurrentUserId())
                        && String(right.name) === 'Admin';

                    return `
                        <label class="admin-right-item">
                            <input
                                type="checkbox"
                                data-admin-user-right
                                data-user-id="${escapeHtml(selectedUser.id)}"
                                data-right-id="${escapeHtml(right.id)}"
                                ${checked ? 'checked' : ''}
                                ${isSelfAdminRight ? 'disabled' : ''}
                            >
                            <span>${escapeHtml(right.name)}</span>
                        </label>
                    `;
                }).join('')}
            </div>
        </div>

        <div class="admin-user-section">
            <h4>Project Access</h4>
            <form class="admin-project-access-form" id="adminProjectAccessForm">
                <select class="form-select form-select-sm" name="project_id" ${addableProjects.length ? '' : 'disabled'}>
                    ${addableProjects.map((project) => `
                        <option value="${escapeHtml(project.id)}">${escapeHtml(project.name)} (${escapeHtml(project.id)})</option>
                    `).join('')}
                </select>
                <button class="btn btn-sm btn-primary" type="submit" ${addableProjects.length ? '' : 'disabled'}>
                    <i class="fa-solid fa-plus me-1"></i>
                    Add Access
                </button>
            </form>

            <div class="admin-project-access-list">
                ${explicitAccess.map((access) => {
                    const project = getAdminProject(access.project_id);

                    return `
                        <div class="admin-project-access-item">
                            <span>${escapeHtml(project?.name ?? access.project_id)} <small>${escapeHtml(access.project_id)}</small></span>
                            <button class="btn btn-sm btn-outline-danger" type="button" data-admin-project-access-delete="${escapeHtml(access.id)}" title="Remove access">
                                <i class="fa-solid fa-trash-can"></i>
                            </button>
                        </div>
                    `;
                }).join('') || '<div class="text-secondary">No explicit project access yet.</div>'}
            </div>

            ${ownerProjects.length ? `
                <div class="admin-project-owner-list">
                    <strong>Owner access</strong>
                    ${ownerProjects.map((project) => `
                        <span>${escapeHtml(project.name)} <small>${escapeHtml(project.id)}</small></span>
                    `).join('')}
                </div>
            ` : ''}
        </div>
    `;
}

async function setAdminUserRight(userId, rightId, enabled) {
    const result = await apiPost('/api/admin_options.php', {
        api: 'SetUserRight'
    }, {
        user_id: userId,
        right_id: rightId,
        enabled: enabled ? 1 : 0
    });

    if (!result.success) {
        alert(result.error || 'Could not update user right.');
        return false;
    }

    await loadAdminOptions();
    return true;
}

async function grantAdminProjectAccess(userId, projectId) {
    const result = await apiPost('/api/admin_options.php', {
        api: 'GrantProjectAccess'
    }, {
        user_id: userId,
        project_id: projectId
    });

    if (!result.success) {
        alert(result.error || 'Could not add project access.');
        return false;
    }

    await loadAdminOptions();
    return true;
}

async function revokeAdminProjectAccess(accessId) {
    const result = await apiPost('/api/admin_options.php', {
        api: 'RevokeProjectAccess',
        access_id: accessId
    });

    if (!result.success) {
        alert(result.error || 'Could not remove project access.');
        return false;
    }

    await loadAdminOptions();
    return true;
}

async function saveAdminOptionForm(kind, form, optionId = null) {
    const formData = new FormData(form);
    formData.set('kind', kind);

    if (optionId) {
        formData.set('id', optionId);
    }

    const result = await apiPostForm('/api/admin_options.php', {
        api: optionId ? 'UpdateOption' : 'CreateOption'
    }, formData);

    if (!result.success) {
        alert(result.error || 'Could not save option.');
        return false;
    }

    if (kind === 'type' && !optionId && result.option_id) {
        selectedAdminTaskType = String(result.option_id);
    }

    taskLookupCache.types = null;
    taskLookupCache.priorities = null;
    await populateTaskOptionSelects();
    await loadAdminOptions();

    return true;
}

async function deleteAdminOption(kind, optionId) {
    const result = await apiPost('/api/admin_options.php', {
        api: 'DeleteOption',
        kind,
        id: optionId
    });

    if (!result.success) {
        alert(result.error || 'Could not delete option.');
        return false;
    }

    if (kind === 'type' && String(selectedAdminTaskType) === String(optionId)) {
        selectedAdminTaskType = null;
    }

    taskLookupCache.types = null;
    taskLookupCache.priorities = null;
    await populateTaskOptionSelects();
    await loadAdminOptions();

    return true;
}

async function saveAdminCustomFieldForm(form, fieldId = null) {
    if (!selectedAdminTaskType) {
        alert('Select a task type first.');
        return false;
    }

    const data = Object.fromEntries(new FormData(form));
    data.task_type = selectedAdminTaskType;

    if (fieldId) {
        data.id = fieldId;
    }

    const result = await apiPost('/api/admin_options.php', {
        api: fieldId ? 'UpdateCustomField' : 'CreateCustomField'
    }, data);

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

    await loadAdminOptions();
    return true;
}

async function deleteAdminCustomField(fieldId) {
    const result = await apiPost('/api/admin_options.php', {
        api: 'DeleteCustomField',
        field_id: fieldId
    });

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

    await loadAdminOptions();
    return true;
}

function loadAdmin(section = 'workflows', pushHistory = true) {
    if (document.querySelector('.kiln-app')?.dataset.isAdmin !== '1') {
        loadDashboard(false);
        return;
    }

    const view = showView('adminView');

    if (!view) return;

    if (!['workflows', 'options', 'users'].includes(section)) {
        section = 'workflows';
    }

    document.querySelectorAll('[data-admin-section]').forEach((button) => {
        button.classList.toggle('is-active', button.dataset.adminSection === section);
    });

    document.querySelectorAll('.admin-panel').forEach((panel) => {
        panel.hidden = true;
    });

    const panelBySection = {
        workflows: 'adminWorkflowsPanel',
        options: 'adminOptionsPanel',
        users: 'adminUsersPanel'
    };
    const panel = document.getElementById(panelBySection[section]);
    if (panel) panel.hidden = false;

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

    if (section === 'workflows') {
        loadWorkflowEditor();
    } else if (section === 'options' || section === 'users') {
        loadAdminOptions();
    }
}
