function escapeHtml(value) {
    if (typeof IndexLibrary !== 'undefined' && IndexLibrary.escapeHtml) {
        return IndexLibrary.escapeHtml(value);
    }

    return String(value == null ? "" : value)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;");
}

function sanitizeCssColor(value) {
    const color = String(value == null ? "" : value).trim();

    if (/^#[0-9a-f]{3,8}$/i.test(color)) {
        return color;
    }

    if (/^(rgb|hsl)a?\(\s*[0-9.,%\s/deg]+\)$/i.test(color)) {
        return color;
    }

    if (/^[a-z]{3,30}$/i.test(color)) {
        return color;
    }

    return "transparent";
}

var licensedUsers = 0;
var totalLicenses = 0;
var authenticationHeader = '';
var baseAPIUrl = '';
var extensionId = '';
var orchestratorApiBaseUrl = '';
var orchestratorApiKey = '';
var userguidelink = "";
var userhelplink = "";
let allUsers = [];
let userLookup = {};
let totalnumberOfUsersLicense = 0;
let licensedUsersListInitialized = false;
const licensedUsersState = {
    allDocs: [],
    numberOfUsers: 0,
    filterText: '',
    currentPage: 1,
    pageSize: 2
};
let isDevOpsAssistEnabled = false
let isTimesheetEnabled = false
let isEvolveEnabled = false
let isReleaseEnabled = false
let isSummaryEnabled = false
let isCodeReviewerEnabled = false
let isDeveloperAgentEnabled = false
let isCleoEnabled = false
let isGroomEnabled = false

let allReleaseNotes = []; // Store the full list globally for filtering
let allEvolveWorkItemTypes = []; // Store work item types for Evolve configuration
let allSummarizeFields = []; // Store the full list of summarize fields globally for filtering
let allFeedbackTypeFields = [
    { value: "issues", label: "Issues", checked: true },
    { value: "warnings", label: "Warnings", checked: true },
    { value: "suggestions", label: "Info", checked: true }
];
let existingCodebotPat = '';

let witTypes = [];
let witFieldsCache = {};
let witProcessId = '';
let witProcessName = '';
let witBatchFetchInProgress = false;
let witAllWitsForProcess = [];
let witForceRefreshFromADO = false;
let witFieldChangeQueue = {};
let witSaveTimer = null;

let debounceTimers = {};
function debounce(key, func, delay) {
    return function (...args) {
        clearTimeout(debounceTimers[key]);
        debounceTimers[key] = setTimeout(() => func.apply(this, args), delay);
    };
}

async function witFetchFieldsFromDB(processId) {
    try {
        var organizationId = IndexLibrary.GetAccountId();
        var response = await IndexLibrary.GetRequest({
            endpoint: `/api/Settings/GetWorkItemDefaultFieldsByProcess/${encodeURIComponent(organizationId)}/${encodeURIComponent(processId)}`
        });

        if (!response.ok) {
            return null;
        }

        var data = await response.json();
        return data && Array.isArray(data) ? data : [];
    } catch (e) {
        console.error('Error fetching fields from DB:', e);
        return null;
    }
}

// Convert field data from camelCase to PascalCase for API submission
function witFormatFieldForAPI(fieldData) {
    return {
        OrganizationId: fieldData.organizationId,
        DevOpsProcessId: fieldData.devOpsProcessId,
        DevOpsProcessName: fieldData.devOpsProcessName,
        WorkItemTypeName: fieldData.workItemTypeName,
        FieldReferenceName: fieldData.fieldReferenceName,
        FieldName: fieldData.fieldName,
        FieldType: fieldData.fieldType,
        DefaultValue: fieldData.defaultValue,
        AllowedValues: witStringifyAllowedValues(fieldData.allowedValues),
        CreatedBy: fieldData.createdBy
    };
}

// Store WIT fields to database
async function witStoreFieldsToDB(fieldsToStore) {
    if (!fieldsToStore || fieldsToStore.length === 0) return;

    try {
        var payload = {
            organizationId: IndexLibrary.GetAccountId(),
            devOpsProcessId: witProcessId,
            devOpsProcessName: witProcessName,
            createdBy: IndexLibrary.GetUserEmail(),
            items: fieldsToStore.map(function (fieldData) {
                return {
                    workItemTypeName: fieldData.workItemTypeName,
                    fieldReferenceName: fieldData.fieldReferenceName,
                    fieldName: fieldData.fieldName,
                    fieldType: fieldData.fieldType,
                    defaultValue: fieldData.defaultValue || '',
                    allowedValues: witStringifyAllowedValues(fieldData.allowedValues),
                    devOpsProcessName: witProcessName,
                    createdBy: IndexLibrary.GetUserEmail()
                };
            })
        };

        var response = await IndexLibrary.PostRequest({
            endpoint: "/api/Settings/BulkUpsertWorkItemDefaultFields",
            body: payload
        });

        if (!response.ok) {
            throw new Error('Bulk upsert failed with status ' + response.status);
        }
    } catch (e) {
        console.error('Error storing fields to DB:', e);
    }
}

// Map ADO field types to DB-compatible types
function witMapFieldType(adoType) {
    if (!adoType) return 'String';
    var typeMap = {
        'string': 'String',
        'integer': 'integer',
        'double': 'decimal',
        'decimal': 'decimal',
        'boolean': 'boolean',
        'dateTime': 'dateTime',
        'html': 'html',
        'identity': 'identity',
        'guid': 'guid'
    };
    return typeMap[adoType.toLowerCase()] || adoType;
}

async function apiSave(endpoint, payload, successMsg) {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.PostRequest({
        endpoint: endpoint,
        body: payload
    })
        .then(res => res.ok ? IndexLibrary.ShowToast('success', successMsg) : Promise.reject())
        .catch(() => IndexLibrary.ShowToast('error', 'Error saving configuration'));
}

async function saveSettingsData() {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    apiSave('/api/Settings/Upsert', {
        organizationName: organizationName,
        organizationId: IndexLibrary.GetAccountId(),
        releaseTaskScopeLevel: $("#scopeProjectCheck").is(":checked") ? 1 : 0,
        pageSizeForTables: $("#pageSizeForTables").val(),
        createdBy: IndexLibrary.GetUserDisplayName()
    }, 'Settings saved successfully.');
}

function saveEvolveConfigurationsData() {
    apiSave('/api/Settings/UpdateEvolveConfigurations', {
        OrganizationId: IndexLibrary.GetAccountId(),
        EvolveWorkItemTypes: allEvolveWorkItemTypes.filter(wit => wit.checked).map(wit => wit.value).join(','),
        CreatedBy: IndexLibrary.GetUserDisplayName()
    }, 'Evolve configurations saved successfully.');
}

function saveSummarizeConfigurationsData() {
    apiSave('/api/Settings/UpdateSummarizeConfigurations', {
        OrganizationId: IndexLibrary.GetAccountId(),
        SummarizeFields: allSummarizeFields.filter(field => field.checked).map(field => field.value).join(','),
        CreatedBy: IndexLibrary.GetUserDisplayName()
    }, 'Summarize configurations saved successfully.');
}

// 5-minute increments from 5 minutes up to 3 hours (180 minutes).
const QUICK_LOG_BUTTON_MINUTE_OPTIONS = Array.from({ length: 36 }, (_, i) => (i + 1) * 5);
const DEFAULT_QUICK_LOG_BUTTON_MINUTES = [15, 30, 45, 60];

function formatQuickLogOptionLabel(minutes) {
    const hours = Math.floor(minutes / 60);
    const mins = minutes % 60;
    if (hours === 0) return `${mins}m`;
    if (mins === 0) return `${hours}h`;
    return `${hours}h${mins}m`;
}

function populateQuickLogButtonSelects() {
    const optionsHtml = QUICK_LOG_BUTTON_MINUTE_OPTIONS
        .map(minutes => `<option value="${minutes}">${formatQuickLogOptionLabel(minutes)}</option>`)
        .join('');

    $(".quickLogButtonSelect").each(function (index) {
        $(this).html(optionsHtml);
        $(this).val(DEFAULT_QUICK_LOG_BUTTON_MINUTES[index]);
    });
}

function getQuickLogButtonMinutesFromForm() {
    return [1, 2, 3, 4].map(n => parseInt($(`#quickLogButton${n}`).val(), 10));
}

function setQuickLogButtonMinutesOnForm(minutes) {
    const values = Array.isArray(minutes) && minutes.length === 4 ? minutes : DEFAULT_QUICK_LOG_BUTTON_MINUTES;
    values.forEach((value, index) => {
        $(`#quickLogButton${index + 1}`).val(value);
    });
}

function findDuplicateQuickLogButtonMinutes(minutesList) {
    const seen = new Set();
    for (const minutes of minutesList) {
        if (seen.has(minutes)) return minutes;
        seen.add(minutes);
    }
    return null;
}

function saveTimesheetConfigurationsData() {
    const quickLogButtonMinutes = getQuickLogButtonMinutesFromForm();
    const duplicate = findDuplicateQuickLogButtonMinutes(quickLogButtonMinutes);
    if (duplicate !== null) {
        IndexLibrary.ShowToast('error', `${formatQuickLogOptionLabel(duplicate)} already exists — Quick Log button values must be unique.`);
        return;
    }

    apiSave('/api/TimesheetSettings/UpsertSettings', {
        defaultWorkLogTime: parseInt($("#workLogDefaultTime").val()),
        allWorkLogsBillableByDefault: $("#chkIsBillableByDefault").is(':checked'),
        organizationId: IndexLibrary.GetAccountId(),
        reportScopeLevel: $("#scopeProjectCheck").is(":checked") ? 1 : 0,
        quickLogButtonMinutes: quickLogButtonMinutes,
        workItemFieldMaps: [{
            fieldName: "Customer",
            mappedField: $('#customerMapping').find(":selected").val(),
            organizationId: IndexLibrary.GetAccountId(),
            ExtensionId: "perfect-timesheet"
        }],
        updatedBy: IndexLibrary.GetUserEmail()
    }, 'Timesheet configurations saved successfully.');
}

function SelectUserFromSuggestion(item) {
    $("#txtUserFilter").val($(item).data('displayName') + " (" + $(item).data('email') + ")");
    $("#txtUserFilter").data('email', $(item).data('email'));
    $("#txtUserFilter").data('displayName', $(item).data('displayName'));
    $("#userSuggestions").hide();
    $("#btnAddLicense").removeAttr('disabled');
}




async function CreateTrialLicense() {
    await IndexLibrary.CreateTrialLicense(true);
}



async function PopulateWorkItemFieldsAndConfig() {
    const orgName = await IndexLibrary.GetAccountNameAsync();
    const [workItemFields, releaseConfig, summarizeConfig, fieldMappings] = await Promise.all([
        IndexLibrary.GetRequest({
            endpoint: "/api/AzureDevOps/GetWorkItemFields",
        }).then(r => r.json()),
        IndexLibrary.GetRequest({
            endpoint: "/api/Settings/GetReleaseConfigurations",
        }).then(r => r.json()),
        IndexLibrary.GetRequest({
            endpoint: "/api/Settings/GetSummarizeConfigurations",
        }).then(r => r.json()),
        IndexLibrary.GetRequest({
            endpoint: "/api/TimesheetSettings/GetWorkItemFieldMappings",
        }).then(r => r.json())
    ]);

    var fieldOptionsHtml = "<option value='None'><-- No Mapping --></option>";
    workItemFields.value.forEach(element => {
        fieldOptionsHtml += `<option value="${escapeHtml(element.referenceName)}">${escapeHtml(element.name)}</option>`;
    });
    $("#startDateMapping, #endDateMapping, #effortMapping, #customerMapping").html(fieldOptionsHtml);

    const options = Array.from(document.getElementById('effortMapping').options);
    const releaseNotes = options.map((opt, i) => ({
        id: `releaseNote${i}`, value: opt.value, label: opt.text, checked: false
    }));
    allReleaseNotes = releaseNotes;
    allSummarizeFields = releaseNotes.map(n => ({ ...n, id: n.id.replace('releaseNote', 'summarizeField') }));

    applyReleaseConfig(releaseConfig, releaseNotes);

    if (summarizeConfig?.summarizeFields) {
        const selected = summarizeConfig.summarizeFields.split(',');
        allSummarizeFields = allSummarizeFields.map(f => ({ ...f, checked: selected.includes(f.value) }));
    }
    populateSummarizeFieldsDropdown(allSummarizeFields);
    updateSummarizeFieldsDropdownLabel();

    var customerMappedField = fieldMappings.find(x => x.fieldName == 'Customer')?.mappedField;
    $("#customerMapping").val(customerMappedField);
}

async function fetchOrgProcessWorkItemTypeNames() {
    const names = new Set();
    try {
        const orgName = await IndexLibrary.GetAccountNameAsync();

        const processesRes = await fetch(
            'https://dev.azure.com/' + orgName + '/_apis/work/processes?$expand=projects&api-version=7.0',
            { headers: witAuth() }
        );
        if (!processesRes.ok) return names;

        const processes = (await processesRes.json()).value || [];
        const usedProcesses = processes.filter(p => (p.projects || []).length > 0);

        const witResults = await Promise.all(usedProcesses.map(p =>
            fetch('https://dev.azure.com/' + orgName + '/_apis/work/processes/' + p.typeId + '/workItemTypes?api-version=7.0', { headers: witAuth() })
                .then(r => r.ok ? r.json() : { value: [] })
                .catch(() => ({ value: [] }))
        ));

        witResults.forEach(result => {
            (result.value || []).forEach(w => {
                if (w && w.name) names.add(w.name.toLowerCase());
            });
        });
    } catch (e) {
        console.error('Error fetching organization process work item types:', e);
    }
    return names;
}

async function PopulateEvolveWorkItemTypesAndConfig(refreshCache = false) {
    const orgName = await IndexLibrary.GetAccountNameAsync();
    const witUrl = refreshCache
        ? "/api/AzureDevOps/GetAllWorkItemTypes?refreshCache=true"
        : "/api/AzureDevOps/GetAllWorkItemTypes";

    const [workItemTypes, evolveConfig] = await Promise.all([
        IndexLibrary.GetRequest({
            endpoint: witUrl,
        }).then(r => r.json()),
        IndexLibrary.GetRequest({
            endpoint: "/api/Settings/GetEvolveConfigurations",
        }).then(r => r.json()).catch(() => null)
    ]);

    const savedTypes = evolveConfig?.evolveWorkItemTypes;
    const hasSavedConfig = !!(savedTypes && savedTypes.trim().length);
    const selectedTypes = hasSavedConfig ? savedTypes.split(',') : [];

    const defaultTypeNames = hasSavedConfig ? new Set() : await fetchOrgProcessWorkItemTypeNames();

    const evolveWorkItemTypes = workItemTypes.map((wit, i) => ({
        id: `evolveWIT${i}`, value: wit.name, label: wit.name,
        checked: hasSavedConfig
            ? selectedTypes.includes(wit.name)
            : defaultTypeNames.has((wit.name || '').toLowerCase())
    }));

    allEvolveWorkItemTypes = evolveWorkItemTypes;
    populateEvolveWorkItemTypesDropdown(evolveWorkItemTypes);
    updateEvolveWorkItemTypesDropdownLabel();

    if (!hasSavedConfig) {
        const defaultSelected = evolveWorkItemTypes.filter(wit => wit.checked).map(wit => wit.value);
        if (defaultSelected.length) {
            IndexLibrary.PostRequest({
                endpoint: '/api/Settings/UpdateEvolveConfigurations',
                body: {
                    OrganizationId: IndexLibrary.GetAccountId(),
                    EvolveWorkItemTypes: defaultSelected.join(','),
                    CreatedBy: IndexLibrary.GetUserDisplayName()
                }
            }).catch(e => console.error('Failed to persist default Evolve configurations:', e));
        }
    }
}

async function refreshEvolveWorkItemTypes() {
    const btn = document.getElementById('btnRefreshEvolveWIT');
    const originalHtml = btn ? btn.innerHTML : null;
    if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> Refreshing...'; }
    try {
        await PopulateEvolveWorkItemTypesAndConfig(true);
        IndexLibrary.ShowToast('success', 'Work item types refreshed successfully.');
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error refreshing work item types: ' + e.message);
    } finally {
        if (btn) { btn.disabled = false; btn.innerHTML = originalHtml; }
    }
}

// System fields excluded from form display 
const WIT_NON_FORM_FIELDS = new Set([
    'System.Id', 'System.AreaId', 'System.IterationId', 'System.Rev',
    'System.AuthorizedDate', 'System.RevisedDate', 'System.Watermark',
    'System.TeamProject', 'System.PersonId', 'System.AttachedFileCount',
    'System.ExternalLinkCount', 'System.HyperLinkCount', 'System.CommentCount',
    'System.AuthorizedAs', 'System.BoardColumn', 'System.BoardColumnDone',
    'System.BoardLane', 'System.ChangedDate', 'System.NodeName',
    'System.Title', 'System.State', 'System.History',
    'System.Description', 'System.Parent', 'System.RemoteLinkCount', 'System.WorkItemType'
]);

function witIsNonFormSystemField(refName) {
    if (!refName) return false;
    if (WIT_NON_FORM_FIELDS.has(refName)) return true;
    return /^System\.(Area|Iteration)Level\d+$/.test(refName);
}

const WIT_SYSTEM_PAGE_TYPES = new Set(['history', 'links', 'attachments']);

const WIT_DYNAMIC_ALLOWED_VALUE_FIELDS = new Set([
    'Microsoft.VSTS.Build.FoundIn', 'Microsoft.VSTS.Build.IntegrationBuild'
]);

function witEsc(str) { return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
function witSafeUrl(url) { var v = String(url == null ? '' : url).trim(); return /^(https?:|data:image\/)/i.test(v) ? witEsc(v) : ''; }
function witEscJs(value) { return witEsc(JSON.stringify(String(value == null ? '' : value))); }
function witEscAttr(str) { return String(str).replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
function witSanitizeId(str) { return String(str).replace(/[^a-zA-Z0-9-_]/g, '-'); }
function witAuth() { return { 'Authorization': 'Bearer ' + authenticationHeader }; }

function witParseAllowedValues(raw) {
    if (Array.isArray(raw)) return raw;
    if (typeof raw === 'string' && raw.trim().startsWith('[')) {
        try { return JSON.parse(raw); } catch (e) { return []; }
    }
    return [];
}

function witStringifyAllowedValues(values) {
    var arr = witParseAllowedValues(values);
    return arr.length > 0 ? JSON.stringify(arr) : null;
}

function witBaseType(type) {
    var t = String(type || 'string');
    if (t.indexOf('picklist') === 0) {
        var b = t.slice(8).toLowerCase();
        return (b === 'integer' || b === 'double') ? b : 'string';
    }
    return t;
}

async function witFetchProjectForProcess(orgName) {
    try {
        var res = await fetch('https://dev.azure.com/' + orgName + '/_apis/work/processes/' + witProcessId + '?$expand=projects&api-version=7.0', { headers: witAuth() });
        if (!res.ok) return null;
        var projects = (await res.json()).projects || [];
        return projects.length ? projects[0].name : null;
    } catch (e) { return null; }
}

async function witFetchProcessChainIds(orgName) {
    var ids = [witProcessId];
    var current = witProcessId;
    for (var i = 0; i < 5; i++) {
        try {
            var res = await fetch('https://dev.azure.com/' + orgName + '/_apis/work/processes/' + current + '?api-version=7.0', { headers: witAuth() });
            if (!res.ok) break;
            var pid = (await res.json()).parentProcessTypeId;
            if (!pid || pid === '00000000-0000-0000-0000-000000000000' || ids.indexOf(pid) !== -1) break;
            ids.push(pid);
            current = pid;
        } catch (e) { break; }
    }
    return ids;
}

async function witFetchFieldTypeMap(orgName) {
    var map = {};
    try {
        var res = await fetch('https://dev.azure.com/' + orgName + '/_apis/wit/fields?api-version=7.0', { headers: witAuth() });
        if (res.ok) ((await res.json()).value || []).forEach(function (f) { map[f.referenceName] = f; });
    } catch (e) { }
    return map;
}

function witResolveFieldType(meta) {
    if (!meta) return 'String';
    if (meta.isIdentity) return 'identity';
    return witMapFieldType(witBaseType(meta.type));
}

async function witFetchWitFields(orgName, projectName, witName) {
    try {
        var res = await fetch('https://dev.azure.com/' + orgName + '/' + encodeURIComponent(projectName) + '/_apis/wit/workitemtypes/' + encodeURIComponent(witName) + '/fields?$expand=allowedValues&api-version=7.0', { headers: witAuth() });
        return res.ok ? ((await res.json()).value || []) : [];
    } catch (e) { return []; }
}

async function witRefreshFromADO(orgName) {
    var projectName = await witFetchProjectForProcess(orgName);
    if (!projectName) return;
    var typeMap = await witFetchFieldTypeMap(orgName);
    var items = [];
    await Promise.all(witAllWitsForProcess.map(async function (wit) {
        var fields = await witFetchWitFields(orgName, projectName, wit.name);
        fields.forEach(function (f) {
            if (!f.alwaysRequired || witIsNonFormSystemField(f.referenceName)) return;
            items.push({
                workItemTypeName: wit.name,
                fieldReferenceName: f.referenceName,
                fieldName: f.name,
                fieldType: witResolveFieldType(typeMap[f.referenceName]),
                allowedValues: witParseAllowedValues(f.allowedValues)
            });
        });
    }));
    if (items.length) await witStoreFieldsToDB(items);
}

function witParseLayout(layout) {
    var map = new Map();
    (layout.pages || []).forEach(function (page) {
        if (page && page.pageType && WIT_SYSTEM_PAGE_TYPES.has(String(page.pageType).toLowerCase())) return;
        (page.sections || []).forEach(function (section) {
            (section.groups || []).forEach(function (group) {
                (group.controls || []).forEach(function (control) {
                    if (control && control.id && !control.isContribution && control.visible !== false) {
                        map.set(control.id, control.label || '');
                    }
                });
            });
        });
    });
    return map;
}

async function witFetchLayoutFromProcess(orgName, processId, witRefName) {
    var base = 'https://dev.azure.com/' + orgName + '/_apis/work/processes/' + processId + '/workItemTypes/' + encodeURIComponent(witRefName);
    try {
        var res = await fetch(base + '?$expand=layout&api-version=7.0', { headers: witAuth() });
        if (res.ok) {
            var wit = await res.json();
            if (wit && wit.layout && (wit.layout.pages || []).length) return witParseLayout(wit.layout);
        }
    } catch (e) { }
    try {
        var res2 = await fetch(base + '/layout?api-version=7.0', { headers: witAuth() });
        if (res2.ok) return witParseLayout(await res2.json());
    } catch (e) { }
    return null;
}

async function witFetchLayoutFieldRefs(orgName, witRefName, processChainIds) {
    var ids = processChainIds && processChainIds.length ? processChainIds : [witProcessId];
    for (var i = 0; i < ids.length; i++) {
        var map = await witFetchLayoutFromProcess(orgName, ids[i], witRefName);
        if (map) return map;
    }
    return null;
}

async function witFetchAllFormFields(orgName) {
    var result = {};
    var setup = await Promise.all([
        witFetchProjectForProcess(orgName),
        witFetchFieldTypeMap(orgName),
        witFetchProcessChainIds(orgName)
    ]);
    var projectName = setup[0], typeMap = setup[1], processChainIds = setup[2];
    if (!projectName) return result;
    await Promise.all(witAllWitsForProcess.map(async function (wit) {
        var results = await Promise.all([
            witFetchWitFields(orgName, projectName, wit.name),
            witFetchLayoutFieldRefs(orgName, wit.referenceName || wit.name, processChainIds)
        ]);
        var fields = results[0], layoutMap = results[1];
        var list = [];
        fields.forEach(function (f) {
            if (f.readOnly || witIsNonFormSystemField(f.referenceName)) return;
            if (layoutMap && !layoutMap.has(f.referenceName)) return;
            var meta = typeMap[f.referenceName] || {};
            var allowedValues = WIT_DYNAMIC_ALLOWED_VALUE_FIELDS.has(f.referenceName)
                ? [] : witParseAllowedValues(f.allowedValues);
            var layoutLabel = layoutMap ? layoutMap.get(f.referenceName) : '';
            list.push({
                referenceName: f.referenceName,
                name: layoutLabel || f.name,
                type: witResolveFieldType(meta),
                alwaysRequired: !!f.alwaysRequired,
                allowedValues: allowedValues,
                isDbField: true
            });
        });
        result[wit.name] = list;
    }));
    return result;
}

function witBuildSavedDefaultsMap(dbFields) {
    var map = {};
    (dbFields || []).forEach(function (f) {
        if (f.chatId || witIsDeletedField(f)) return;
        (map[f.workItemTypeName] = map[f.workItemTypeName] || {})[f.fieldReferenceName] = f.defaultValue || '';
    });
    return map;
}

function witLoadCacheFromDB(dbFields) {
    witFieldsCache = {};
    (dbFields || []).forEach(function (f) {
        if (f.chatId) return;
        (witFieldsCache[f.workItemTypeName] = witFieldsCache[f.workItemTypeName] || []).push({
            referenceName: f.fieldReferenceName,
            name: f.fieldName,
            type: f.fieldType,
            alwaysRequired: true,
            defaultValue: f.defaultValue,
            isDeleted: f.isDeleted,
            isDbField: true,
            allowedValues: witParseAllowedValues(f.allowedValues)
        });
    });
}

async function witFetchProcesses() {
    try {
        var orgName = await IndexLibrary.GetAccountNameAsync();
        var res = await fetch(
            'https://dev.azure.com/' + orgName + '/_apis/work/processes?api-version=7.0',
            { headers: witAuth() }
        );
        if (!res.ok) return [];
        var data = await res.json();
        return (data.value || []).sort(function (a, b) { return a.name.localeCompare(b.name); });
    } catch (e) {
        console.error('Error fetching processes:', e);
        return [];
    }
}


function witHasConfigurableFields(witName) {
    var fields = witFieldsCache[witName] || [];
    if (!fields.length) return false;

    var filtered = witFilterFields(fields, {});
    return filtered.length > 0;
}

function witIsDeletedField(field) {
    if (!field) return false;
    return field.isDeleted === true || field.isDeleted === 1 || field.isDeleted === '1';
}

function witFilterFields(fields, savedMap) {
    return fields.filter(function (f) {
        if (witIsDeletedField(f)) return false;
        if (f.referenceName === 'System.Title') return false;
        if (f.referenceName === 'System.State') return false;
        if (savedMap[f.referenceName]) return false;
        if (!f.isDbField && f.defaultValue !== undefined && f.defaultValue !== null && f.defaultValue !== '') return false;
        return true;
    });
}



function witQueueFieldChange(witName, fieldRefName, fieldValue) {
    if (fieldRefName === 'System.Title' || fieldRefName === 'System.State') return;

    var field = (witFieldsCache[witName] || []).find(function (f) { return f.referenceName === fieldRefName; });
    if (!field) return;

    if (!field.isDbField && field.defaultValue !== undefined && field.defaultValue !== null && field.defaultValue !== '') return;

    var queueKey = witName + '|' + fieldRefName;
    witFieldChangeQueue[queueKey] = {
        organizationId: IndexLibrary.GetAccountId(),
        devOpsProcessId: witProcessId,
        devOpsProcessName: witProcessName,
        workItemTypeName: witName,
        fieldReferenceName: field.referenceName,
        fieldName: field.name,
        fieldType: witMapFieldType(field.type),
        defaultValue: fieldValue,
        allowedValues: field.allowedValues,
        createdBy: IndexLibrary.GetUserDisplayName()
    };

    // Clear existing timer and set new one
    if (witSaveTimer) clearTimeout(witSaveTimer);
    witSaveTimer = setTimeout(function () {
        witFlushFieldChangeQueue();
    }, 700); // Batch save after 1.5 seconds of inactivity
}

async function witFlushFieldChangeQueue() {
    var fieldsToSave = Object.keys(witFieldChangeQueue).map(function (key) { return witFieldChangeQueue[key]; });

    if (fieldsToSave.length === 0) return;

    console.log('Batch saving ' + fieldsToSave.length + ' field changes...');
    witFieldChangeQueue = {}; // Clear queue

    var savePromises = fieldsToSave.map(function (fieldData) {
        var apiPayload = witFormatFieldForAPI(fieldData);
        return IndexLibrary.PostRequest({
            endpoint: "/api/Settings/UpsertWorkItemDefaultField",
            body: apiPayload
        });
    });

    Promise.all(savePromises).then(function (responses) {
        var allOk = responses.every(function (res) { return res.ok; });
        if (allOk) {
            console.log('Successfully saved ' + fieldsToSave.length + ' field defaults.');
            IndexLibrary.ShowToast('success', 'Field defaults saved successfully. (' + fieldsToSave.length + ' fields)');
        } else {
            console.error('Error saving some batch fields');
            IndexLibrary.ShowToast('error', 'Error saving some field defaults');
        }
    }).catch(function (e) {
        console.error('Error batch saving field changes:', e);
        IndexLibrary.ShowToast('error', 'Error: ' + e.message);
    });
}

// Build input HTML for a field
function witBuildInput(witName, field, idx, savedValue) {
    var wid = witSanitizeId(witName);
    var id = 'wit-f-' + wid + '-' + idx;
    var ref = witEscAttr(field.referenceName);
    var ro = field.readOnly ? 'disabled' : '';
    var val = witEsc(String(savedValue || ''));

    if (field.allowedValues && field.allowedValues.length > 0) {
        var opts = ['',].concat(field.allowedValues).map(function (v) {
            return '<option value="' + witEscAttr(v) + '" ' + (v === savedValue ? 'selected' : '') + '>' + witEsc(v || '-- no default --') + '</option>';
        }).join('');
        return '<select class="form-select form-select-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" ' + ro + '>' + opts + '</select>';
    }

    // Handle field types from database
    var fieldType = (field.type || '').toLowerCase();

    switch (fieldType) {
        case 'boolean':
            return '<select class="form-select form-select-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" ' + ro + '>'
                + '<option value=""' + (savedValue === '' ? ' selected' : '') + '>-- no default --</option>'
                + '<option value="true"' + (savedValue === 'true' ? ' selected' : '') + '>True</option>'
                + '<option value="false"' + (savedValue === 'false' ? ' selected' : '') + '>False</option></select>';
        case 'integer':
        case 'int':
        case 'double':
        case 'decimal':
            return '<input type="number" class="form-control form-control-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" value="' + val + '" placeholder="Enter default value..." ' + ro + '>';
        case 'datetime':
        case 'date':
            return '<input type="date" class="form-control form-control-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" value="' + val + '" ' + ro + '>';
        case 'html':
            return '<textarea class="form-control form-control-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" rows="3" placeholder="Enter default HTML content..." ' + ro + '>' + val + '</textarea>';
        case 'identity':
            return '<input type="text" data-identity="true" autocomplete="off" class="form-control form-control-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" value="' + val + '" placeholder="Search user..." ' + ro + '>';
        case 'string':
        case 'text':
        default:
            return '<input type="text" class="form-control form-control-sm wit-input" id="' + id + '" data-ref="' + ref + '" data-wit="' + wid + '" value="' + val + '" placeholder="Enter default value..." ' + ro + '>';
    }
}

var witIdentityPeople = null;
async function witFetchIdentityPeople(orgName) {
    if (witIdentityPeople) return witIdentityPeople;
    try {
        var allUsers = await IndexLibrary.getAllUsers();
        var lookup = IndexLibrary.buildUserLookup(allUsers);
        witIdentityPeople = Object.values(lookup)
            .filter(function (u) { return u && u.name && u.email; })
            .map(function (u) { return { displayName: u.name, mail: u.email }; })
            .sort(function (a, b) { return a.displayName.localeCompare(b.displayName); });
    } catch (e) {
        witIdentityPeople = [];
    }
    return witIdentityPeople;
}

function witWireIdentityInputs(root) {
    (root || document).querySelectorAll('input[data-identity="true"]').forEach(function (input) {
        if (input.dataset.identityWired === 'true') return;
        input.dataset.identityWired = 'true';

        var menu = document.createElement('div');
        menu.style.cssText = 'position:absolute;z-index:100000;background:var(--da-surface-raised, #fff);color:var(--da-text, #000);border:1px solid #ccc;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.15);max-height:240px;overflow-y:auto;display:none;';
        document.body.appendChild(menu);

        function hide() { menu.style.display = 'none'; }
        function position() {
            var r = input.getBoundingClientRect();
            menu.style.left = (r.left + window.scrollX) + 'px';
            menu.style.top = (r.bottom + window.scrollY) + 'px';
            menu.style.width = r.width + 'px';
        }
        function renderList() {
            var q = input.value.trim().toLowerCase();
            var people = witIdentityPeople || [];
            var items = (q ? people.filter(function (u) { return (u.displayName + ' ' + u.mail).toLowerCase().includes(q); }) : people).slice(0, 50);
            if (!items.length) { hide(); return; }
            menu.innerHTML = items.map(function (u) {
                var tooltipText = u.displayName.toLowerCase() !== u.mail.toLowerCase() ? (u.displayName + '\n' + u.mail) : u.displayName;
                var avatarLink = IndexLibrary.getCachedAvatar(u.mail);
                var avatarHtml = avatarLink
                    ? '<img src="' + witEscAttr(avatarLink) + '" class="avatar-img" />'
                    : '<span class="avatar">' + witEsc(IndexLibrary.getInitials(u.displayName)) + '</span>';
                return '<div class="wit-id-item user-cell" data-val="' + witEscAttr(u.displayName) + '" title="' + witEscAttr(tooltipText) + '" style="padding:6px 10px;cursor:pointer;">'
                    + avatarHtml
                    + '<span class="user-name">' + witEsc(u.displayName) + '<span class="user-email"> &lt;' + witEsc(u.mail) + '&gt;</span></span>'
                    + '</div>';
            }).join('');
            position();
            menu.style.display = 'block';

            IndexLibrary.loadAvatarsFor(items.map(function (u) { return u.mail; }))
                .then(function (loaded) {
                    if (loaded && menu.style.display !== 'none') renderList();
                });
        }

        input.addEventListener('focus', renderList);
        input.addEventListener('input', renderList);
        menu.addEventListener('mousedown', function (e) {
            var item = e.target.closest('.wit-id-item');
            if (!item) return;
            e.preventDefault();
            input.value = item.getAttribute('data-val');
            input.dispatchEvent(new Event('change', { bubbles: true }));
            hide();
        });
        document.addEventListener('mousedown', function (e) {
            if (e.target === input || menu.contains(e.target)) return;
            hide();
        });
        window.addEventListener('scroll', function (e) {
            if (menu.style.display === 'none' || menu.contains(e.target)) return;
            position();
        }, true);
    });
}

function witHasIdentityField() {
    return Object.keys(witFieldsCache).some(function (wn) {
        return (witFieldsCache[wn] || []).some(function (f) { return (f.type || '').toLowerCase() === 'identity'; });
    });
}

function showLoadingIndicator(container, message) {
    message = message || 'Loading required fields...';
    container.innerHTML = '<div style="display:flex;align-items:center;gap:8px;color:#666;font-size:13px;">'
        + '<span class="spinner-border spinner-border-sm" role="status"></span>'
        + '<span>' + witEsc(message) + '</span>'
        + '</div>';
}

// Hide loader
function witHideMinimalLoader(container) {
    container.innerHTML = '';
}

async function loadWorkItemSettings() {
    var container = document.getElementById('workItemSettingsContainer');
    if (!container) return;
    try {
        var orgName = await IndexLibrary.GetAccountNameAsync();
        if (!orgName) throw new Error('Organization not available');

        // Fetch processes list (1 API call)
        var processes = await witFetchProcesses();

        if (!processes.length) {
            container.innerHTML = '<div class="alert alert-warning">No processes found.</div>';
            return;
        }

        // Render process selector with Refresh button
        container.innerHTML =
            '<div class="mb-3 d-flex align-items-end gap-3" style="justify-content: space-between;">'
            + '  <div style="width: 250px;">'
            + '    <label class="form-label">Select Process:</label>'
            + '    <select id="witProcessSelect" class="form-select" onchange="onWitProcessSelected(this.value)">'
            + '      <option value="">-- Choose a process --</option>'
            + processes.map(function (p) { return '<option value="' + witEscAttr(p.typeId) + '|' + witEscAttr(p.name) + '">' + witEsc(p.name) + '</option>'; }).join('')
            + '    </select>'
            + '  </div>'
            + '  <div id="witRefreshWrap" style="display:none;align-items:center;gap:8px;">'
            + '    <button id="witRefreshBtn" class="btn btn-primary" onclick="refreshWitData()" style="height: 38px; min-width: 100px;">Refresh Field List</button>'
            + '    <i class="bi bi-info-circle" style="color:#0078d4;cursor:help;font-size:16px;" title="If you changed your organization\'s process and added, removed, or modified fields in the process, you can use the refresh button to update the field list below."></i>'
            + '  </div>'
            + '</div>'
            + '<div id="wit-content" style="display:none;"></div>';

        // Restore previously selected process from this session, if still valid
        var savedVal = sessionStorage.getItem('witSelectedProcess');
        if (savedVal && processes.some(function (p) { return (p.typeId + '|' + p.name) === savedVal; })) {
            var select = document.getElementById('witProcessSelect');
            select.value = savedVal;
            await onWitProcessSelected(savedVal);
        }
    } catch (e) {
        container.innerHTML = '<div class="alert alert-danger">' + witEsc(e.message) + '</div>';
    }
}

async function onWitProcessSelected(val) {
    var content = document.getElementById('wit-content');
    var btn = document.getElementById('witRefreshWrap');
    if (!val) {
        sessionStorage.removeItem('witSelectedProcess');
        content.style.display = 'none'; btn.style.display = 'none'; return;
    }
    sessionStorage.setItem('witSelectedProcess', val);

    // Flush any pending changes before switching processes
    if (witSaveTimer) clearTimeout(witSaveTimer);
    witFlushFieldChangeQueue();

    var parts = val.split('|');
    witProcessId = parts[0];
    witProcessName = parts[1];
    witTypes = []; witFieldsCache = {};
    witAllWitsForProcess = [];
    witBatchFetchInProgress = false;

    btn.style.display = 'flex';
    await loadWitProcess();
}

async function refreshWitData() {
    if (witSaveTimer) clearTimeout(witSaveTimer);
    witFlushFieldChangeQueue();

    witTypes = []; witFieldsCache = {};
    witAllWitsForProcess = []; // Reset all WITs list
    witBatchFetchInProgress = false; // Reset batch state
    witForceRefreshFromADO = true;    // Force fetch from ADO on refresh
    await loadWitProcess();
}

// Load WITs for the selected process: check DB first, then ADO if needed
async function loadWitProcess() {
    var content = document.getElementById('wit-content');
    content.style.display = '';

    try {
        var orgName = await IndexLibrary.GetAccountNameAsync();

        showLoadingIndicator(content, 'Loading process data...');

        if (!witProcessId) throw new Error('Process ID not available');

        var witRes = await fetch('https://dev.azure.com/' + orgName + '/_apis/work/processes/' + witProcessId + '/workItemTypes?api-version=7.0', { headers: witAuth() });
        if (!witRes.ok) throw new Error('HTTP ' + witRes.status);

        var processWits = ((await witRes.json()).value || []);

        witAllWitsForProcess = processWits.map(function (w) {
            return { name: w.name, icon: w.icon, referenceName: w.referenceName || w.name };
        }).sort(function (a, b) { return a.name.localeCompare(b.name); });

        try {
            var iconProjectName = await witFetchProjectForProcess(orgName);
            if (iconProjectName) {
                var projectWitsRes = await fetch('https://dev.azure.com/' + orgName + '/' + encodeURIComponent(iconProjectName) + '/_apis/wit/workitemtypes?api-version=7.0', { headers: witAuth() });
                if (projectWitsRes.ok) {
                    var projectWitMap = {};
                    ((await projectWitsRes.json()).value || []).forEach(function (w) { projectWitMap[w.name] = w; });
                    witAllWitsForProcess.forEach(function (w) {
                        var proj = projectWitMap[w.name];
                        if (proj && proj.icon && proj.icon.url) w.icon = proj.icon;
                    });
                }
            }
        } catch (e) {
            console.warn('Could not fetch project icons:', e);
        }

        showLoadingIndicator(content, 'Loading fields from Azure DevOps ...');
        var dbFields = await witFetchFieldsFromDB(witProcessId);
        var savedDefaults = witBuildSavedDefaultsMap(dbFields);

        var adoFieldsByWit = await witFetchAllFormFields(orgName);
        witFieldsCache = {};
        Object.keys(adoFieldsByWit).forEach(function (witName) {
            var savedForWit = savedDefaults[witName] || {};
            witFieldsCache[witName] = adoFieldsByWit[witName].map(function (f) {
                return Object.assign({}, f, { defaultValue: savedForWit[f.referenceName] || '' });
            });
        });
        witForceRefreshFromADO = false;

        var witsWithConfigurableFields = witAllWitsForProcess.filter(function (w) {
            var hasConfigurable = witHasConfigurableFields(w.name);
            return hasConfigurable;
        });

        console.log('WITs with configurable fields:', witsWithConfigurableFields);

        witTypes = witsWithConfigurableFields;

        if (witTypes.length === 0) {
            content.innerHTML = '<div class="text-muted">No work item types with configurable fields were found for this process.</div>';
            return;
        }

        if (witHasIdentityField()) await witFetchIdentityPeople(orgName);

        // Step 6: Render the filtered WITs with fields
        witHideMinimalLoader(content);
        renderWitTable(content);
    } catch (e) {
        console.error('Error in loadWitProcess:', e);
        content.innerHTML = '<div class="alert alert-danger">' + witEsc(e.message) + '</div>';
    }
}

// Render WIT list with auto-expanded required fields inline
function renderWitTable(container) {
    if (!witTypes.length) {
        container.innerHTML = '<div class="text-muted">No Work Item Types found.</div>';
        return;
    }

    var witHtml = '';
    witTypes.forEach(function (wit) {
        var iconUrl = (wit.icon && wit.icon.url) ? wit.icon.url : '';
        var icon = iconUrl ? '<img src="' + witSafeUrl(iconUrl) + '" alt="" style="width:16px;height:16px;flex-shrink:0;"> ' : '';
        var wid = witSanitizeId(wit.name);
        witHtml += '<div style="margin-bottom:24px;border:1px solid #ddd;border-radius:4px;overflow:hidden;">'
            + '<div class="wit-section-header" onclick="toggleWitSection(\'' + wid + '\')" style="background:#f8f9fa;padding:12px 16px;display:flex;align-items:center;gap:8px;font-weight:500;cursor:pointer;user-select:none;">'
            + '<i id="wit-chevron-' + wid + '" class="bi bi-chevron-right" style="font-size:12px;transition:transform .15s;"></i>'
            + icon + '<span style="flex:1;">' + witEsc(wit.name) + '</span></div>'
            + '<div id="wit-fc-' + wid + '" style="padding:16px;border-top:1px solid #ddd;display:none;"></div></div>';
    });

    container.innerHTML = witHtml;

    // Render fields for all WITs
    witTypes.forEach(function (wit) {
        var wid = witSanitizeId(wit.name);
        var fc = document.getElementById('wit-fc-' + wid);
        if (fc) renderWitFields(wit.name, fc);
    });

    witWireIdentityInputs(container);
}

function toggleWitSection(wid) {
    var fc = document.getElementById('wit-fc-' + wid);
    var chevron = document.getElementById('wit-chevron-' + wid);
    if (!fc) return;
    var isOpen = fc.style.display !== 'none';
    fc.style.display = isOpen ? 'none' : '';
    if (chevron) chevron.className = isOpen ? 'bi bi-chevron-right' : 'bi bi-chevron-down';
}

// Render fields inline with filtering
function renderWitFields(witName, container) {
    var fields = witFieldsCache[witName] || [];
    var savedMap = {};
    var filtered = witFilterFields(fields.filter(function (f) { return f && f.name && f.referenceName; }), savedMap);

    // Header with icon
    var wit = witTypes.find(function (w) { return w.name === witName; });
    var iconUrl = (wit && wit.icon && wit.icon.url) ? wit.icon.url : '';
    var icon = iconUrl ? '<img src="' + iconUrl + '" alt="" style="width:20px;height:20px;flex-shrink:0;"> ' : '';
    var header = '';

    if (!filtered.length) {
        container.innerHTML = header + '<div class="text-muted">No configurable fields for this work item type.</div>';
        return;
    }

    var rows = filtered.map(function (field, idx) {
        var fieldType = field.type || field.typeName || 'String';
        var requiredMark = field.alwaysRequired ? '<span style="color:#dc3545;margin-left:2px;">*</span>' : '';
        return '<tr><td style="width:40%;vertical-align:middle;">'
            + '<div><div style="font-weight:500;">' + witEsc(field.name) + requiredMark + '</div>'
            + '<div style="font-size:11px;color:#888;">' + witEsc(field.referenceName) + '</div></div></td>'
            + '<td style="width:60%;vertical-align:middle;">' + witBuildInput(witName, field, idx, savedMap[field.referenceName] || field.defaultValue || '') + '</td></tr>';
    }).join('');

    container.innerHTML = header
        + '<table class="table table-bordered table-sm mb-0">'
        + '<thead class="table-light"><tr><th style="width:40%;">Field Name</th><th style="width:60%;">Default Value</th></tr></thead>'
        + '<tbody>' + rows + '</tbody></table>';

    // Attach change handlers to all field inputs for queued batch save
    var inputs = container.querySelectorAll('.wit-input');
    inputs.forEach(function (input) {
        var fieldRefName = input.getAttribute('data-ref');

        // Single handler - no debounce needed, batching handles throttling
        input.addEventListener('change', function () {
            witQueueFieldChange(witName, fieldRefName, input.value);
        });
    });
}

async function saveReleaseConfigurationsData() {
    var workItemFieldMappings = [];
    workItemFieldMappings.push({
        fieldName: "StartDate", mappedField: $('#startDateMapping').find(":selected").val(), extensionId: "release-manager", organizationId: IndexLibrary.GetAccountId()
    });
    workItemFieldMappings.push({
        fieldName: "EndDate", mappedField: $('#endDateMapping').find(":selected").val(), extensionId: "release-manager", organizationId: IndexLibrary.GetAccountId()
    });
    workItemFieldMappings.push({
        fieldName: "Effort", mappedField: $('#effortMapping').find(":selected").val(), extensionId: "release-manager", organizationId: IndexLibrary.GetAccountId()
    });

    let releaseNotesFields = allReleaseNotes
        .filter(note => note.checked)
        .map(note => note.value);

    // Join with comma
    const releaseNotesCsv = releaseNotesFields.join(',');
    workItemFieldMappings.push({
        fieldName: "ReleaseNotes", mappedField: releaseNotesCsv, extensionId: "release-manager", organizationId: IndexLibrary.GetAccountId()
    });

    const payload = {
        organizationId: IndexLibrary.GetAccountId(),
        workingHoursPerDay: $("#numberOfWorkingHours").val(),
        workingDaysPerWeek: $("#numberOfWorkingDays").val(),
        workItemFieldMaps: workItemFieldMappings,
        createdBy: IndexLibrary.GetUserDisplayName()
    };

    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.PostRequest({
        endpoint: "/api/Settings/UpdateReleaseManagerConfigurations",
        body: payload
    })
        .then((response) => response.json())
        .then(function (response) {
            IndexLibrary.ShowToast('success', 'Release Settings saved successfully.');

        });
}

async function PopulatePublicHolidays() {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    return IndexLibrary.GetRequest({
        endpoint: "/api/Settings/GetPublicHolidays",
    })
        .then((response) => response.json())
        .then(function (publicHolidays) {
            var holidaysHtml = "<table style='width:70%'><thead><th>Date</th><th>Description</th><th></th></thead><tbody>";
            publicHolidays.forEach(element => {
                var dHoliday = new Date(element.holidayDate);
                holidaysHtml = holidaysHtml + "<tr><td>" + dHoliday.toLocaleDateString("en-US") + "</td><td>" + element.description + "</td><td><button class='btn btn-primary btnDeleteHoliday' style='--bs-btn-padding-y:0; font-size:12px; background-color: #0078d4;' onclick=DeleteHoliday('" + element.holidayDate + "')><i class='bi bi-trash' style='color:white; padding-right:8px;'></i>Remove</button></td></tr>";
            });
            holidaysHtml = holidaysHtml + "</tbody></table>";
            $("#publicHolidaysContainer").html(holidaysHtml);
        });

}

async function AddMoreLicenses() {
    const currentText = $("#numberOfLicensedUsers").text();
    const matches = currentText.match(/(-?\d+)\s\/\s(-?\d+)\s\((-?\d+)\sAvailable\)/);
    if (!matches) return;
    const allocated = parseInt(matches[1]);
    const total = parseInt(matches[2]);

    const { value: numberOfLicenses } = await IndexLibrary.showCustomDialog({
        title: '',
        customClass: {
            popup: 'license-popup'
        },
        html: `
            <div class="license-header">
                <span class="license-title">DevOps Assist AI Licenses:</span>
                <span class="license-count">${currentText}</span>
            </div>

            <div class="license-input-wrapper">
               <input
                id="swalLicensesInput"
                type="number"
                value="${total}"
                step="1"
                class="license-input"
                onkeydown="return blockInvalidNumberKeys(event)"
                />
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        focusConfirm: false,
        preConfirm: () => {
            const value = parseInt(
                document.getElementById('swalLicensesInput').value
            );

            if (isNaN(value)) {
                IndexLibrary.showValidationMessage('Please enter a valid number');
                return false;
            }

            if (value < 0) {
                IndexLibrary.showValidationMessage('Negative numbers are not allowed');
                return false;
            }

            if (value < allocated) {
                IndexLibrary.showValidationMessage(
                    `Cannot reduce licenses below allocated (${allocated})`
                );
                return false;
            }

            if (value === total) {
                IndexLibrary.showValidationMessage(`The new license value must be different from the current value (${value})`);
                return false;
            }

            return value;
        }
    });

    if (numberOfLicenses) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddMoreLicenses?numberOfLicenses=${numberOfLicenses}`,
        })
            .then(res => res.json())
            .then(data => {
                $("#numberOfLicensedUsers").text(
                    `${data.allocatedLicenses} / ${data.totalLicenses} (${data.availableLicenses} Available)`
                );
                IndexLibrary.ShowToast('success', 'Licenses updated successfully.');
                applyAddUserAvailability();
            });
    }
}
async function AddMoreTimesheetLicenses() {
    const currentText = $("#numberOfTimesheetLicenses").text();
    const matches = currentText.match(/(-?\d+)\s\/\s(-?\d+)\s\((-?\d+)\sAvailable\)/);
    if (!matches) return;

    const allocated = parseInt(matches[1]);
    const total = parseInt(matches[2]);

    const { value: numberOfLicenses } = await IndexLibrary.showCustomDialog({
        title: '',
        customClass: {
            popup: 'license-popup'
        },
        html: `
            <div class="license-header">
                <span class="license-title">Timesheet Licenses:</span>
                <span class="license-count">${currentText}</span>
            </div>

            <div class="license-input-wrapper">
                <input 
                    id="swalTimesheetInput"
                    type="number"
                    value="${total}"
                    step="1"
                    class="license-input"
                    onkeydown="return blockInvalidNumberKeys(event)"
                />
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        focusConfirm: false,
        preConfirm: () => {
            const value = parseInt(
                document.getElementById('swalTimesheetInput').value
            );

            if (isNaN(value)) {
                IndexLibrary.showValidationMessage('Please enter a valid number');
                return false;
            }

            if (value < 0) {
                IndexLibrary.showValidationMessage('Negative numbers are not allowed');
                return false;
            }

            if (value < allocated) {
                IndexLibrary.showValidationMessage(
                    `Cannot reduce licenses below allocated (${allocated})`
                );
                return false;
            }

            if (value === total) {
                IndexLibrary.showValidationMessage(`The new license value must be different from the current value (${value})`);
                return false;
            }

            return value;
        }
    });

    if (numberOfLicenses) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddMoreTimesheetLicenses?numberOfLicenses=${numberOfLicenses}`,
        })
            .then(res => res.json())
            .then(data => {
                $("#numberOfTimesheetLicenses").text(
                    `${data.allocatedLicenses} / ${data.totalLicenses} (${data.availableLicenses} Available)`
                );
                applyAddUserAvailability();
                IndexLibrary.ShowToast('success', 'Timesheet licenses updated successfully.');
            });
    }
}
async function AddMoreCodeReviewerLicenses() {
    const currentText = $("#numberOfCodeReviewerLicenses").text();
    const matches = currentText.match(/(-?\d+)\s\/\s(-?\d+)\s\((-?\d+)\sAvailable\)/);
    if (!matches) return;

    const allocated = parseInt(matches[1]);
    const total = parseInt(matches[2]);

    const { value: numberOfLicenses } = await IndexLibrary.showCustomDialog({
        title: '',
        customClass: {
            popup: 'license-popup'
        },
        html: `
            <div class="license-header">
                <span class="license-title">Code Reviewer Licenses:</span>
                <span class="license-count">${currentText}</span>
            </div>

            <div class="license-input-wrapper">
                <input 
                    id="swalCodeReviewInput"
                    type="number"
                    value="${total}"
                    step="1"
                    class="license-input"
                    onkeydown="return blockInvalidNumberKeys(event)"
                />
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        focusConfirm: false,
        preConfirm: () => {
            const value = parseInt(
                document.getElementById('swalCodeReviewInput').value
            );

            if (isNaN(value)) {
                IndexLibrary.showValidationMessage('Please enter a valid number');
                return false;
            }

            if (value < 0) {
                IndexLibrary.showValidationMessage('Negative numbers are not allowed');
                return false;
            }

            if (value < allocated) {
                IndexLibrary.showValidationMessage(
                    `Cannot reduce licenses below allocated (${allocated})`
                );
                return false;
            }

            if (value === total) {
                IndexLibrary.showValidationMessage(`The new license value must be different from the current value (${value})`);
                return false;
            }

            return value;
        }
    });

    if (numberOfLicenses) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddMoreCodeReviewerLicenses?numberOfLicenses=${numberOfLicenses}`,
        })
            .then(response => response.json())
            .then(data => {
                $("#numberOfCodeReviewerLicenses").text(
                    `${data.allocatedLicenses} / ${data.totalLicenses} (${data.availableLicenses} Available)`
                );
                IndexLibrary.ShowToast('success', 'Code Reviewer licenses updated successfully.');
            });
    }
}
async function AddMoreDeveloperAgentLicenses() {
    const currentText = $("#numberOfDeveloperAgentLicenses").text();
    const matches = currentText.match(/(-?\d+)\s\/\s(-?\d+)\s\((-?\d+)\sAvailable\)/);
    if (!matches) return;

    const allocated = parseInt(matches[1]);
    const total = parseInt(matches[2]);

    const { value: numberOfLicenses } = await IndexLibrary.showCustomDialog({
        title: '',
        customClass: {
            popup: 'license-popup'
        },
        html: `
            <div class="license-header">
                <span class="license-title">Developer Agent Licenses:</span>
                <span class="license-count">${currentText}</span>
            </div>

            <div class="license-input-wrapper">
                <input
                    id="swalDeveloperAgentInput"
                    type="number"
                    value="${total}"
                    step="1"
                    class="license-input"
                    onkeydown="return blockInvalidNumberKeys(event)"
                />
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        focusConfirm: false,
        preConfirm: () => {
            const value = parseInt(
                document.getElementById('swalDeveloperAgentInput').value
            );

            if (isNaN(value)) {
                IndexLibrary.showValidationMessage('Please enter a valid number');
                return false;
            }

            if (value < 0) {
                IndexLibrary.showValidationMessage('Negative numbers are not allowed');
                return false;
            }

            if (value < allocated) {
                IndexLibrary.showValidationMessage(
                    `Cannot reduce licenses below allocated (${allocated})`
                );
                return false;
            }

            if (value === total) {
                IndexLibrary.showValidationMessage(`The new license value must be different from the current value (${value})`);
                return false;
            }

            return value;
        }
    });

    if (numberOfLicenses) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddMoreDeveloperAgentLicenses?numberOfLicenses=${numberOfLicenses}`,
        })
            .then(response => response.json())
            .then(data => {
                $("#numberOfDeveloperAgentLicenses").text(
                    `${data.allocatedLicenses} / ${data.totalLicenses} (${data.availableLicenses} Available)`
                );
                IndexLibrary.ShowToast('success', 'Developer Agent licenses updated successfully.');
            });
    }
}
async function AddMorePrototypeAgentLicenses() {
    const currentText = $("#numberOfPrototypeAgentLicenses").text();
    const matches = currentText.match(/(-?\d+)\s\/\s(-?\d+)\s\((-?\d+)\sAvailable\)/);
    if (!matches) return;

    const allocated = parseInt(matches[1]);
    const total = parseInt(matches[2]);

    const { value: numberOfLicenses } = await IndexLibrary.showCustomDialog({
        title: '',
        customClass: {
            popup: 'license-popup'
        },
        html: `
            <div class="license-header">
                <span class="license-title">Prototype Agent Licenses:</span>
                <span class="license-count">${currentText}</span>
            </div>

            <div class="license-input-wrapper">
                <input
                    id="swalPrototypeAgentInput"
                    type="number"
                    value="${total}"
                    step="1"
                    class="license-input"
                    onkeydown="return blockInvalidNumberKeys(event)"
                />
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        focusConfirm: false,
        preConfirm: () => {
            const value = parseInt(
                document.getElementById('swalPrototypeAgentInput').value
            );

            if (isNaN(value)) {
                IndexLibrary.showValidationMessage('Please enter a valid number');
                return false;
            }

            if (value < 0) {
                IndexLibrary.showValidationMessage('Negative numbers are not allowed');
                return false;
            }

            if (value < allocated) {
                IndexLibrary.showValidationMessage(
                    `Cannot reduce licenses below allocated (${allocated})`
                );
                return false;
            }

            if (value === total) {
                IndexLibrary.showValidationMessage(`The new license value must be different from the current value (${value})`);
                return false;
            }

            return value;
        }
    });

    if (numberOfLicenses) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddMorePrototypeAgentLicenses?numberOfLicenses=${numberOfLicenses}`,
        })
            .then(response => response.json())
            .then(data => {
                $("#numberOfPrototypeAgentLicenses").text(
                    `${data.allocatedLicenses} / ${data.totalLicenses} (${data.availableLicenses} Available)`
                );
                IndexLibrary.ShowToast('success', 'Prototype Agent licenses updated successfully.');
            });
    }
}
async function AddMoreCleoLicenses() {
    const currentText = $("#numberOfCleoLicenses").text();
    const matches = currentText.match(/(-?\d+)\s\/\s(-?\d+)\s\((-?\d+)\sAvailable\)/);
    if (!matches) return;

    const allocated = parseInt(matches[1]);
    const total = parseInt(matches[2]);

    const { value: numberOfLicenses } = await IndexLibrary.showCustomDialog({
        title: '',
        customClass: {
            popup: 'license-popup'
        },
        html: `
            <div class="license-header">
                <span class="license-title">Cleo Agent Licenses:</span>
                <span class="license-count">${currentText}</span>
            </div>

            <div class="license-input-wrapper">
                <input
                    id="swalCleoInput"
                    type="number"
                    value="${total}"
                    step="1"
                    class="license-input"
                    onkeydown="return blockInvalidNumberKeys(event)"
                />
            </div>
        `,
        showCancelButton: true,
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        focusConfirm: false,
        preConfirm: () => {
            const value = parseInt(
                document.getElementById('swalCleoInput').value
            );

            if (isNaN(value)) {
                IndexLibrary.showValidationMessage('Please enter a valid number');
                return false;
            }

            if (value < 0) {
                IndexLibrary.showValidationMessage('Negative numbers are not allowed');
                return false;
            }

            if (value < allocated) {
                IndexLibrary.showValidationMessage(
                    `Cannot reduce licenses below allocated (${allocated})`
                );
                return false;
            }

            if (value === total) {
                IndexLibrary.showValidationMessage(`The new license value must be different from the current value (${value})`);
                return false;
            }

            return value;
        }
    });

    if (numberOfLicenses) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddMoreCleoLicenses?numberOfLicenses=${numberOfLicenses}`,
        })
            .then(response => response.json())
            .then(data => {
                $("#numberOfCleoLicenses").text(
                    `${data.allocatedLicenses} / ${data.totalLicenses} (${data.availableLicenses} Available)`
                );
                IndexLibrary.ShowToast('success', 'Cleo Agent licenses updated successfully.');
            });
    }
}
async function DeleteHoliday(holidayDate) {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.PostRequest({
        endpoint: `/api/Settings/DeletePublicHoliday?holidayDate=${holidayDate}&createdBy=${IndexLibrary.GetUserDisplayName()}`,
    })
        .then((response) => response.text())
        .then(function (data) {
            if (data == 'true') {
                IndexLibrary.ShowToast('success', 'Holiday removed successfully.');
                PopulatePublicHolidays();
            }
        });
}

async function ExtendLicense() {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    var extensionOptions = [];
    for (var i = 1; i <= 12; i++) {
        extensionOptions.push(i.toString());
    }
    IndexLibrary.showCustomDialog({
        title: '',
        input: 'select',
        inputLabel: 'Number of months',
        inputOptions: extensionOptions,
        showCancelButton: true,
        inputValidator: (value) => {
            if (!value) {
                return 'Number cannot be empty'
            }
        }
    }).then(function (result) {
        IndexLibrary.PutRequest({
            endpoint: `/api/License/ExtendLicense?numberOfMonths=${parseInt(result.value) + 1}`,
        })
            .then((response) => response.json())
            .then(function (data) {
                //$("#numberOfLicensedUsers").text(data.allocatedLicenses + " / " + data.totalLicenses + " (" + (data.availableLicenses) + " Available)");
                var licenseExpiryDate = new Date(data.expiryDateTimeUtc);
                var now = new Date();
                var formattedDate = licenseExpiryDate.toLocaleDateString("en-US");
                if (licenseExpiryDate < now) {
                    $("#licenseExpiryDateTime").text(`${formattedDate} (Expired)`);
                } else {
                    $("#licenseExpiryDateTime").text(`${formattedDate} (Active)`);
                }
                IndexLibrary.ShowToast('success', 'License extended successfully.');
            });
    })
}
async function AddMoreOpenAITokens(userEmail) {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.showCustomDialog({
        title: '',
        input: 'text',
        inputLabel: 'Number of OpenAI tokens',
        inputValue: 1,
        showCancelButton: true,
        inputValidator: (value) => {
            if (!value || parseInt(value) <= 0) {
                return 'Number must be a positive integer'
            }
        }
    }).then(function (result) {

        if (!result.isConfirmed) return;

        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddOpenAIMonthlyTokens?userEmail=${encodeURIComponent(userEmail)}&numberOfTokens=${encodeURIComponent(result.value)}`,
        })
            .then(response => {
                if (!response.ok) {
                    throw new Error("Request failed: " + response.status);
                }
                return response.json();
            })
            .then(function () {
                LoadMonthlyTokenUsage(userEmail);
                IndexLibrary.ShowToast('success', 'OpenAI tokens added successfully.');
            })
            .catch(err => {
                console.error(err);
            });
    });
}
async function AddMoreAgentTokens(userEmail) {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.showCustomDialog({
        title: '',
        input: 'text',
        inputLabel: 'Number of Agent tokens',
        inputValue: 1,
        showCancelButton: true,
        inputValidator: (value) => {
            if (!value || parseInt(value) <= 0) {
                return 'Number must be a positive integer';
            }
        }
    }).then(function (result) {
        if (!result.isConfirmed) return;

        IndexLibrary.PutRequest({
            endpoint: `/api/License/AddAgentMonthlyTokens?userEmail=${encodeURIComponent(userEmail)}&numberOfTokens=${encodeURIComponent(result.value)}`,
        })
            .then(response => {
                if (!response.ok) throw new Error("Request failed: " + response.status);
                return response.json();
            })
            .then(() => {
                LoadMonthlyAgentTokenUsage(userEmail);
                IndexLibrary.ShowToast('success', 'Agent tokens added successfully.');
            })
            .catch(err => {
                console.error(err);
            });
    });

}

function GetCodebotAIConfigurations() {
    if (!orchestratorApiBaseUrl) return;
    var orgId = IndexLibrary.GetAccountId();
    return IndexLibrary.GetRequest({
        baseAPIUrl: orchestratorApiBaseUrl,
        endpoint: `/api/AgentConfiguration/azuredevops?organizationId=${orgId}`,
        xApiKey: orchestratorApiKey
    })
        .then(function (response) {
            if (response.status === 404) {
                return null;
            }
            if (!response.ok) {
                throw new Error('Failed to load Agent configurations');
            }
            return response.json();
        })
        .then(function (data) {
            if (!data) return;
            if (data.personalAccessToken) {
                // PAT exists in DB — enable validate, but do NOT show the stored PAT in the field
                existingCodebotPat = data.personalAccessToken;
                document.getElementById('btnValidatePat').disabled = false;
                $("#codebotPersonalAccessToken").attr("placeholder", "********************");
            }
            populateReviewAgentFeedbackTypesDropdown(data.reviewAgentFeedbackTypes);
        })
        .catch(function (error) {
            console.log("Error fetching Agent configurations:", error);
        });
}

function populateReviewAgentFeedbackTypesDropdown(selectedFields) {
    const container = document.getElementById('reviewAgentFeedbackTypesCheckboxContainer');
    const dropdownBtn = document.getElementById("dropdownReviewAgentFeedbackTypes");

    if (!container || !dropdownBtn) {
        console.warn('Review Agent feedback types dropdown container not found');
        return;
    }

    const selectedValues = selectedFields
        ? selectedFields.split(",").map(v => v.trim())
        : [];

    allFeedbackTypeFields.forEach(f => {
        f.checked = selectedValues.length
            ? selectedValues.includes(f.value)
            : false;
    });

    container.innerHTML = '';

    allFeedbackTypeFields.forEach(field => {
        const wrapper = document.createElement("div");
        wrapper.className = "form-check";

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'form-check-input review-feedback-type';
        checkbox.value = field.value;
        checkbox.checked = field.checked;
        checkbox.addEventListener("change", () => {
            field.checked = checkbox.checked;
            updateReviewAgentDropdownText();
        });

        const label = document.createElement('label');
        label.className = 'form-check-label';
        label.setAttribute('for', field.id);
        label.innerText = field.label;
        label.style.marginLeft = '8px';

        wrapper.appendChild(checkbox);
        wrapper.appendChild(label);
        container.appendChild(wrapper);
    });

    updateReviewAgentDropdownText();
}

function updateReviewAgentDropdownText() {
    const dropdownBtn = document.getElementById("dropdownReviewAgentFeedbackTypes");

    const selectedLabels = allFeedbackTypeFields
        .filter(f => f.checked)
        .map(f => f.label);

    dropdownBtn.textContent = selectedLabels.length
        ? selectedLabels.join(", ")
        : "<-- No selection -->";
}

async function saveCodebotAIConfigurationsData() {
    var orgName = await IndexLibrary.GetAccountNameAsync();
    var orgUrl = `https://dev.azure.com/${orgName}`;
    var pat = $("#codebotPersonalAccessToken").val()?.trim() || existingCodebotPat;
    const selectedFeedbackTypes = getSelectedReviewAgentFeedbackTypes();

    var payload = {
        organizationId: IndexLibrary.GetAccountId(),
        organizationName: orgName,
        organizationUrl: orgUrl,
        personalAccessToken: pat,
        reviewAgentFeedbackTypes: selectedFeedbackTypes,
        createdBy: IndexLibrary.GetUserDisplayName()
    };

    IndexLibrary.PostRequest({
        baseAPIUrl: orchestratorApiBaseUrl,
        endpoint: "/api/AgentConfiguration/azuredevops",
        xApiKey: orchestratorApiKey,
        body: payload
    })
        .then(function (response) {
            if (!response.ok) {
                return response.text().then(function (errorText) {
                    throw new Error(errorText || 'Failed to save configuration');
                });
            }
            return response.text();
        })
        .then(function () {
            IndexLibrary.ShowToast('success', 'Agent configurations saved successfully.');
            existingCodebotPat = pat;
            $("#codebotPersonalAccessToken").val('');  // clear field — write-only
            $("#codebotPersonalAccessToken").attr("placeholder", "********************");
            document.getElementById('btnValidatePat').disabled = false;  // now safe to validate
        })
        .catch(function (error) {
            IndexLibrary.ShowToast('error', 'Error saving Agent configurations: ' + error.message);
        });
}

function getSelectedReviewAgentFeedbackTypes() {
    return allFeedbackTypeFields
        .filter(f => f.checked)
        .map(f => f.value)
        .join(",");
}

// --- Agent Context Files (AI Agents tab) --------------------------------------
// These call the DevOpsAssist API (default base URL), NOT the orchestrator that the rest of
// this tab uses — context files are stored and served by the API alongside AI/Presidio settings.

function getSelectedContextAgent() {
    return document.getElementById('agentContextAgentSelect')?.value || 'Prototype';
}

// The Developer agent's AI service is text-only, so it can only consume .md. Keep the picker in
// step with the server-side rule so the restriction is visible before someone picks a file.
function applyAgentContextFileTypes() {
    const input = document.getElementById('agentContextFileInput');
    const hint = document.getElementById('agentContextFileHint');
    if (!input) return;

    const agent = getSelectedContextAgent();

    // All three agents accept .md + images. Cleo stores only (nothing consumes it yet).
    input.accept = '.md,.png,.jpg,.jpeg,.gif,.webp,.bmp';

    if (!hint) return;

    if (agent === 'Developer') {
        hint.textContent = 'Markdown (.md) and images up to 10 MB each. Images (e.g. architecture diagrams, data models) inform the planning phase.';
    } else if (agent === 'Cleo') {
        hint.textContent = '';
    } else {
        hint.textContent = 'Markdown (.md) and images (png, jpg, gif, webp, bmp) up to 10 MB each.';
    }
}

function formatContextFileSize(bytes) {
    if (bytes === null || bytes === undefined) return '';
    if (bytes < 1024) return bytes + ' B';
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
    return (bytes / 1024 / 1024).toFixed(1) + ' MB';
}

function formatContextModifiedDate(value) {
    if (!value) return '';
    const date = new Date(value);
    return isNaN(date.getTime()) ? '' : date.toLocaleString();
}

async function loadAgentContextFiles() {
    const container = document.getElementById('agentContextFilesGrid');
    if (!container) return;

    applyAgentContextFileTypes();
    updateDeveloperContextModeVisibility();
    const agent = getSelectedContextAgent();

    try {
        const response = await IndexLibrary.GetRequest({
            endpoint: `/api/AgentContext?agent=${encodeURIComponent(agent)}`
        });

        if (!response.ok) throw new Error(`Failed to load context files (${response.status})`);

        const files = await response.json();

        if (!files || files.length === 0) {
            container.innerHTML = '<div class="text-muted" style="font-size:13px;">'
                + 'No context files uploaded for this agent.</div>';
            return;
        }

        container.innerHTML =
            '<table class="table table-sm" style="font-size:13px;">'
            + '<thead><tr>'
            + '<th>Agent</th><th>File Name</th><th>Size</th><th>Modified</th><th>Modified By</th><th></th>'
            + '</tr></thead><tbody>'
            + files.map(function (f) {
                return '<tr>'
                    + '<td>' + witEsc(f.agentType) + '</td>'
                    + '<td>' + witEsc(f.fileName) + '</td>'
                    + '<td>' + witEsc(formatContextFileSize(f.sizeBytes)) + '</td>'
                    + '<td>' + witEsc(formatContextModifiedDate(f.modifiedDate)) + '</td>'
                    + '<td>' + witEsc(f.modifiedBy || '') + '</td>'
                    + '<td style="text-align:right;">'
                    + '<button type="button" class="btn btn-sm btn-link text-danger" '
                    + 'onclick="deleteAgentContextFile(' + Number(f.id) + ')" title="Delete">'
                    + '<i class="bi bi-trash"></i></button>'
                    + '</td>'
                    + '</tr>';
            }).join('')
            + '</tbody></table>';
    } catch (error) {
        console.error('Error loading agent context files:', error);
        container.innerHTML = '<div class="alert alert-danger">' + witEsc(error.message) + '</div>';
    }
}

// The repo-vs-attachment mode toggle is Developer-only. Show it only for that agent, and load its
// current value from the API when shown.
function updateDeveloperContextModeVisibility() {
    const row = document.getElementById('developerContextModeRow');
    if (!row) return;

    const largeRow = document.getElementById('developerLargeReposRow');

    if (getSelectedContextAgent() === 'Developer') {
        row.style.display = '';
        loadDeveloperContextMode();
        if (largeRow) {
            largeRow.style.display = '';
            initLargeRepoProjects();
        }
    } else {
        row.style.display = 'none';
        if (largeRow) largeRow.style.display = 'none';
    }
}

async function loadDeveloperContextMode() {
    const checkbox = document.getElementById('useAttachmentBasedContext');
    if (!checkbox) return;

    try {
        const response = await IndexLibrary.GetRequest({ endpoint: '/api/AgentContext/settings?agent=Developer' });
        if (!response.ok) return;
        const data = await response.json();
        checkbox.checked = !!(data && data.useAttachmentBasedContext);
    } catch (error) {
        console.error('Error loading Developer context mode:', error);
    }
}

async function saveDeveloperContextMode() {
    const checkbox = document.getElementById('useAttachmentBasedContext');
    if (!checkbox) return;

    try {
        const response = await IndexLibrary.PutRequest({
            endpoint: '/api/AgentContext/settings',
            headers: { 'UserEmail': IndexLibrary.GetUserEmail() },
            body: { agentType: 'Developer', useAttachmentBasedContext: checkbox.checked }
        });

        if (!response.ok) throw new Error(`Save failed (${response.status})`);
        IndexLibrary.ShowToast('success', 'Developer context mode saved.');
    } catch (error) {
        IndexLibrary.ShowToast('error', 'Error saving context mode: ' + error.message);
    }
}

// ── Large repositories (Humongous Developer Agent) ──────────────────────────────
// Marked repos are cloned and indexed once at save time, not per run, so the grid shows
// preparation status per repository.

// Repository names are only unique within a project, so the grid is scoped to one project
// at a time. Loading every repo in every project up front does not scale for large orgs.
let largeRepoProjectsLoaded = false;

async function initLargeRepoProjects() {
    const select = document.getElementById('largeRepoProjectSelect');
    if (!select) return;

    // Projects rarely change within a session, but the grid must still refresh when the user
    // switches away from Developer and back — otherwise it shows stale preparation status.
    if (largeRepoProjectsLoaded) {
        await loadLargeRepositories();
        return;
    }

    try {
        const projects = await IndexLibrary.getAllProjectsWithNames();
        if (!projects || projects.length === 0) {
            select.innerHTML = '<option value="">No projects found</option>';
            return;
        }

        select.innerHTML = projects
            .map(p => `<option value="${witEsc(p.id)}" data-name="${witEsc(p.name)}">${witEsc(p.name)}</option>`)
            .join('');
        largeRepoProjectsLoaded = true;
        await loadLargeRepositories();
    } catch (error) {
        console.error('Error loading projects for large repositories:', error);
        select.innerHTML = '<option value="">Could not load projects</option>';
    }
}

function getSelectedLargeRepoProject() {
    const select = document.getElementById('largeRepoProjectSelect');
    const option = select?.selectedOptions?.[0];
    return option ? { id: option.value, name: option.dataset.name || option.text } : null;
}

async function loadLargeRepositories() {
    const grid = document.getElementById('largeRepoGrid');
    const project = getSelectedLargeRepoProject();
    if (!grid || !project || !project.id) return;

    grid.innerHTML = '<div class="text-muted" style="font-size:13px;">Loading repositories&hellip;</div>';

    try {
        // The full repo list comes from Azure DevOps; only the marked subset (and its preparation
        // state) comes from our API, so the two are merged here rather than server-side.
        const [repos, markedResponse] = await Promise.all([
            IndexLibrary.getReposByProject(project.id),
            IndexLibrary.GetRequest({
                endpoint: `/api/AgentContext/large-repos?project=${encodeURIComponent(project.name)}`
            })
        ]);

        const marked = markedResponse.ok ? await markedResponse.json() : [];
        const markedByName = new Map((marked || []).map(m => [String(m.repositoryName).toLowerCase(), m]));

        if (!repos || repos.length === 0) {
            grid.innerHTML = '<div class="text-muted" style="font-size:13px;">No repositories in this project.</div>';
            return;
        }

        grid.innerHTML =
            '<table class="table table-sm" style="font-size:13px;">'
            + '<thead><tr>'
            // "Cache size", not "Size": this is the prepared mirror's footprint on the agent's file
            // share, not the repository's size in Azure DevOps. The mirror is a shallow single-branch
            // bare clone, so it reads far smaller than the repo and invites a false comparison.
            + '<th style="width:40px;"></th><th>Repository</th><th>Status</th><th>Indexed commit</th>'
            + '<th title="Space this repository\'s prepared cache uses on the agent\'s storage. '
            + 'Not the repository size in Azure DevOps.">Cache size</th>'
            + '</tr></thead><tbody>'
            + repos.map(function (r) {
                const state = markedByName.get(String(r.name).toLowerCase());
                const checked = state ? ' checked' : '';
                return '<tr>'
                    + '<td><input type="checkbox" class="form-check-input large-repo-check" '
                    + 'value="' + witEsc(r.name) + '"' + checked + ' /></td>'
                    + '<td>' + witEsc(r.name) + '</td>'
                    + '<td>' + renderLargeRepoStatus(state) + '</td>'
                    // &mdash; as an entity, not a literal em-dash: permissions.html declares no
                    // charset, so the browser falls back to Windows-1252 and a UTF-8 em-dash renders
                    // as "â€”". The entity is encoding-independent. Only the dynamic value is escaped.
                    + '<td>' + (state?.lastIndexedSha ? witEsc(state.lastIndexedSha.substring(0, 8)) : '&mdash;') + '</td>'
                    + '<td>' + (state && state.sizeBytes ? witEsc(formatContextFileSize(state.sizeBytes)) : '&mdash;') + '</td>'
                    + '</tr>';
            }).join('')
            + '</tbody></table>';
    } catch (error) {
        console.error('Error loading large repositories:', error);
        grid.innerHTML = '<div class="alert alert-danger">' + witEsc(error.message) + '</div>';
    }
}

function renderLargeRepoStatus(state) {
    if (!state) return '<span class="text-muted">Not marked</span>';

    switch (state.status) {
        case 'Ready':
            return '<span class="text-success"><i class="bi bi-check-circle-fill"></i> Ready</span>';
        case 'Cloning':
            return '<span class="text-primary"><i class="bi bi-hourglass-split"></i> Cloning&hellip;</span>';
        case 'Indexing':
            return '<span class="text-primary"><i class="bi bi-hourglass-split"></i> Indexing&hellip;</span>';
        case 'Failed':
            // The reason matters more than the label — a failed prepare is otherwise a dead end.
            return '<span class="text-danger" title="' + witEsc(state.statusMessage || '')
                + '"><i class="bi bi-exclamation-triangle-fill"></i> Failed</span>';
        default:
            return '<span class="text-muted">Not prepared</span>';
    }
}

async function saveLargeRepositories() {
    const button = document.getElementById('btnSaveLargeRepos');
    const project = getSelectedLargeRepoProject();
    if (!project || !project.id) return;

    const selected = Array.from(document.querySelectorAll('.large-repo-check:checked'))
        .map(cb => cb.value);

    // Unticking a repository used to delete only its database row, leaving a multi-GB cached clone
    // and its .md pack behind with nothing in the UI pointing at them. Work out what is being
    // removed BEFORE saving, so the user can confirm and choose what to discard.
    const removed = await getUnmarkedLargeRepositories(project, selected);
    let purgeContextPack = false;

    if (removed.length > 0) {
        const choice = await confirmLargeRepositoryRemoval(removed);
        if (!choice.confirmed) return;   // Cancel means "do not remove" — abandon the whole save.
        purgeContextPack = choice.purgeContextPack;
    }

    const original = button ? button.innerHTML : '';
    if (button) {
        button.disabled = true;
        button.innerHTML = '<i class="bi bi-hourglass-split"></i> Saving&hellip;';
    }

    try {
        const response = await IndexLibrary.PutRequest({
            endpoint: '/api/AgentContext/large-repos',
            headers: { 'UserEmail': IndexLibrary.GetUserEmail() },
            body: { projectName: project.name, repositoryNames: selected }
        });

        if (!response.ok) throw new Error(`Save failed (${response.status})`);

        // The selection is saved at this point. Preparation is queued separately via the
        // orchestrator (which owns Service Bus), so a failure there must not read as a failed save —
        // the rows simply stay "Not prepared" and preparation can be retried by saving again.
        const queued = await queueLargeRepositoryPreparation(project, selected);

        // After the save, so cached data is never discarded for a selection change that failed.
        if (removed.length > 0) await queueLargeRepositoryCleanup(project, removed, purgeContextPack);

        // Plain ASCII in toast text: these are passed to ShowToast rather than written as innerHTML,
        // so an HTML entity would show literally and a raw em-dash depends on the page charset.
        IndexLibrary.ShowToast('success', selected.length
            ? (queued
                ? 'Large repositories saved. Preparation has been queued.'
                : 'Large repositories saved, but preparation could not be queued - save again to retry.')
            : 'Large repository selection cleared.');

        await loadLargeRepositories();
    } catch (error) {
        IndexLibrary.ShowToast('error', 'Error saving large repositories: ' + error.message);
    } finally {
        if (button) {
            button.disabled = false;
            button.innerHTML = original;
        }
    }
}

// Repositories currently saved as large that are NOT in the new selection.
// Read fresh from the API rather than from render-time state, so a stale grid cannot cause the wrong
// repository's cache to be deleted.
async function getUnmarkedLargeRepositories(project, selected) {
    try {
        const response = await IndexLibrary.GetRequest({
            endpoint: `/api/AgentContext/large-repos?project=${encodeURIComponent(project.name)}`
        });
        if (!response.ok) return [];

        const marked = (await response.json()) || [];
        const keep = new Set(selected.map(n => String(n).toLowerCase()));

        return marked
            .map(m => m.repositoryName)
            .filter(name => name && !keep.has(String(name).toLowerCase()));
    } catch (error) {
        // Never block a save because the diff could not be computed — the worst case is that cached
        // data lingers, which is exactly the pre-existing behaviour and is reclaimed by LRU eviction.
        console.error('Could not determine which large repositories were unmarked:', error);
        return [];
    }
}

// Confirms removal and asks whether the AI-generated summaries should go too.
// Default is to KEEP them: the clone costs only time to rebuild, the .md pack costs tokens (§4.1),
// so an accidental untick should not be expensive to undo.
async function confirmLargeRepositoryRemoval(removed) {
    const list = removed.map(n => '<li>' + witEsc(n) + '</li>').join('');

    const result = await IndexLibrary.showCustomDialog({
        title: 'Remove from large repositories?',
        html:
            '<div style="text-align:left;font-size:13px;">'
            + '<p>The cached clone for the following will be deleted:</p>'
            + '<ul style="margin:0 0 10px 18px;">' + list + '</ul>'
            + '<p>AI-generated summaries are kept, so adding these back later costs no tokens.</p>'
            + '<label style="display:block;margin-top:10px;">'
            + '<input type="checkbox" id="purgeContextPackCheck"> '
            + 'Also delete AI-generated summaries (adding back will use tokens again)'
            + '</label>'
            + '</div>',
        showCancelButton: true,
        confirmButtonText: 'Remove',
        cancelButtonText: 'Cancel',
        confirmButtonColor: '#0078d4',
        cancelButtonColor: '#8f8f8f',
        focusCancel: true,
        preConfirm: function () {
            const box = document.getElementById('purgeContextPackCheck');
            return { purgeContextPack: !!(box && box.checked) };
        }
    });

    return {
        confirmed: !!(result && result.isConfirmed),
        purgeContextPack: !!(result && result.value && result.value.purgeContextPack)
    };
}

// Cleanup is queued through the orchestrator for the same reason as preparation: it owns Service Bus,
// and deleting a multi-GB mirror outlives an HTTP request. Never throws — the selection is already
// saved by the time this runs.
async function queueLargeRepositoryCleanup(project, repositoryNames, purgeContextPack) {
    if (!repositoryNames || repositoryNames.length === 0) return true;

    if (!orchestratorApiBaseUrl) {
        console.warn('Orchestrator base URL is not configured - repository cleanup was not queued.');
        return false;
    }

    try {
        const response = await IndexLibrary.PostRequest({
            baseAPIUrl: orchestratorApiBaseUrl,
            endpoint: '/api/large-repository/cleanup',
            xApiKey: orchestratorApiKey,
            body: {
                organizationId: IndexLibrary.GetAccountId(),
                organizationName: await IndexLibrary.GetAccountNameAsync(),
                projectId: project.id,
                projectName: project.name,
                repositoryNames: repositoryNames,
                createdBy: IndexLibrary.GetUserEmail(),
                purgeContextPack: purgeContextPack === true
            }
        });

        if (!response.ok) {
            console.error('Queueing repository cleanup failed:', response.status);
            return false;
        }
        return true;
    } catch (error) {
        console.error('Error queueing repository cleanup:', error);
        return false;
    }
}

// Preparation (clone + .md context pack) is queued through the orchestrator because it owns the
// Service Bus connection; DevOpsAssistAPI stores the selection but cannot enqueue.
// Returns true when the request was accepted. Never throws — the selection is already saved by the
// time this runs, so a queueing failure must not be reported as a failed save.
async function queueLargeRepositoryPreparation(project, repositoryNames) {
    if (!repositoryNames || repositoryNames.length === 0) return true;

    if (!orchestratorApiBaseUrl) {
        console.warn('Orchestrator base URL is not configured - repository preparation was not queued.');
        return false;
    }

    try {
        const response = await IndexLibrary.PostRequest({
            baseAPIUrl: orchestratorApiBaseUrl,
            endpoint: '/api/large-repository/prepare',
            xApiKey: orchestratorApiKey,
            body: {
                organizationId: IndexLibrary.GetAccountId(),
                organizationName: await IndexLibrary.GetAccountNameAsync(),
                projectId: project.id,
                projectName: project.name,
                // The full marked set, not just newly-ticked repos: the agent skips any whose stored
                // commit still matches the remote tip, so resending is cheap and self-healing when an
                // earlier prepare request was lost.
                repositoryNames: repositoryNames,
                createdBy: IndexLibrary.GetUserEmail()
            }
        });

        if (!response.ok) {
            console.error('Queueing repository preparation failed:', response.status);
            return false;
        }
        return true;
    } catch (error) {
        console.error('Error queueing repository preparation:', error);
        return false;
    }
}

async function uploadAgentContextFile() {
    const input = document.getElementById('agentContextFileInput');
    const button = document.getElementById('btnUploadAgentContext');
    const file = input?.files?.[0];

    if (!file) {
        IndexLibrary.ShowToast('warning', 'Choose a file to upload.');
        return;
    }

    const formData = new FormData();
    formData.append('agent', getSelectedContextAgent());
    formData.append('file', file);

    if (button) button.disabled = true;

    try {
        const response = await IndexLibrary.PostRequest({
            endpoint: '/api/AgentContext',
            headers: { 'UserEmail': IndexLibrary.GetUserEmail() },
            body: formData
        });

        if (!response.ok) {
            // The API returns a plain-text reason for validation failures — surface it as-is.
            const reason = await response.text();
            throw new Error(reason || `Upload failed (${response.status})`);
        }

        IndexLibrary.ShowToast('success', `'${file.name}' uploaded.`);
        input.value = '';
        await loadAgentContextFiles();
    } catch (error) {
        IndexLibrary.ShowToast('error', 'Error uploading context file: ' + error.message);
    } finally {
        if (button) button.disabled = false;
    }
}

async function deleteAgentContextFile(id) {
    try {
        const response = await IndexLibrary.DeleteRequest({
            endpoint: `/api/AgentContext/${id}`,
            headers: { 'UserEmail': IndexLibrary.GetUserEmail() }
        });

        if (!response.ok) throw new Error(`Delete failed (${response.status})`);

        IndexLibrary.ShowToast('success', 'Context file deleted.');
        await loadAgentContextFiles();
    } catch (error) {
        IndexLibrary.ShowToast('error', 'Error deleting context file: ' + error.message);
    }
}

async function createNewPat() {
    const orgName = await IndexLibrary.GetAccountNameAsync();
    IndexLibrary.openInNewTab(`https://dev.azure.com/${orgName}/_usersSettings/tokens`);
}

async function validatePat() {
    const orgId = IndexLibrary.GetAccountId();
    const pat = $('#codebotPersonalAccessToken').val()?.trim();

    try {
        const response = await IndexLibrary.PostRequest({
            baseAPIUrl: orchestratorApiBaseUrl,
            endpoint: `/api/AgentConfiguration/validatepat?organizationId=${orgId}`,
            xApiKey: orchestratorApiKey,
            body: { pat: pat || null }
        });

        if (!response.ok) {
            throw new Error(`Validation request failed: ${response.status}`);
        }

        const data = await response.json();
        if (data.isValid) {
            IndexLibrary.ShowToast('success', 'PAT is valid');
        } else {
            IndexLibrary.ShowToast('error', 'PAT is invalid or expired ✗ ' + (data.message || ''));
        }
    } catch (error) {
        console.error('Error validating PAT:', error);
        IndexLibrary.ShowToast('error', 'Error validating PAT: ' + error.message);
    }
}


function onAiProviderChange() {
    var provider = document.getElementById('aiProviderSelect').value;
    var isAzure = provider === 'AzureOpenAI';
    var endpointRow = document.getElementById('aiEndpointRow');
    var apiVersionRow = document.getElementById('aiApiVersionRow');
    if (endpointRow) endpointRow.style.display = isAzure ? '' : 'none';
    if (apiVersionRow) apiVersionRow.style.display = isAzure ? '' : 'none';
}

// Show the Key field for API-key auth, or the service-principal fields for Entra ID auth.
function onAiAuthModeChange() {
    var mode = document.getElementById('aiAuthMode')?.value || 'Key';
    var isEntra = mode === 'EntraId';
    var apiKeyRow = document.getElementById('aiApiKeyRow');
    if (apiKeyRow) apiKeyRow.style.display = isEntra ? 'none' : '';
    ['aiTenantIdRow', 'aiClientIdRow', 'aiClientSecretRow'].forEach(function (id) {
        var row = document.getElementById(id);
        if (row) row.style.display = isEntra ? '' : 'none';
    });
}

function setAiKeyStatus(text) {
    var el = document.getElementById(document.getElementById('aiAuthMode')?.value === 'EntraId' ? 'aiSecretStatus' : 'aiKeyStatus');
    if (el) el.textContent = text || '';
}

async function loadAISettings() {
    if (!document.getElementById('aiProviderSelect')) return; // tab not present
    try {
        const res = await IndexLibrary.GetRequest({ endpoint: "/api/AISettings" });
        if (!res.ok) return;
        const data = await res.json();
        if (!data || !data.isConfigured) {
            // Still restore a saved auth-mode selection so the right fields show even before a secret exists.
            if (data && data.authMode) document.getElementById('aiAuthMode').value = data.authMode;
            onAiProviderChange();
            onAiAuthModeChange();
            return;
        }
        if (data.provider) document.getElementById('aiProviderSelect').value = data.provider;
        document.getElementById('aiAuthMode').value = data.authMode || 'Key';
        document.getElementById('aiEndpoint').value = data.endpoint || '';
        document.getElementById('aiApiVersion').value = data.apiVersion || '';
        document.getElementById('aiModel').value = data.modelOrDeployment || '';
        // Secrets are write-only: never returned. Show the masked value as a placeholder if one is stored.
        document.getElementById('aiApiKey').value = '';
        if (data.maskedKey) document.getElementById('aiApiKey').setAttribute('placeholder', data.maskedKey);
        document.getElementById('aiTenantId').value = data.tenantId || '';
        document.getElementById('aiClientId').value = data.clientId || '';
        document.getElementById('aiClientSecret').value = '';
        if (data.maskedClientSecret) document.getElementById('aiClientSecret').setAttribute('placeholder', data.maskedClientSecret);
        setAiKeyStatus(data.keyStatus ? ('Status: ' + data.keyStatus) : '');
        onAiProviderChange();
        onAiAuthModeChange();
    } catch (e) {
        console.error('Failed to load AI settings:', e);
    }
}

function buildAiSettingsPayload() {
    var mode = document.getElementById('aiAuthMode').value || 'Key';
    var payload = {
        provider: document.getElementById('aiProviderSelect').value,
        authMode: mode,
        endpoint: (document.getElementById('aiEndpoint').value || '').trim() || null,
        modelOrDeployment: (document.getElementById('aiModel').value || '').trim(),
        apiVersion: (document.getElementById('aiApiVersion').value || '').trim() || null
    };
    if (mode === 'EntraId') {
        payload.tenantId = (document.getElementById('aiTenantId').value || '').trim() || null;
        payload.clientId = (document.getElementById('aiClientId').value || '').trim() || null;
        var secret = (document.getElementById('aiClientSecret').value || '').trim();
        if (secret) payload.clientSecret = secret; // omit to keep the stored secret
    } else {
        var apiKey = (document.getElementById('aiApiKey').value || '').trim();
        if (apiKey) payload.apiKey = apiKey; // omit to keep the stored key
    }
    return payload;
}

async function saveAISettings() {
    var payload = buildAiSettingsPayload();
    if (!payload.modelOrDeployment) { IndexLibrary.ShowToast('error', 'Model / deployment is required.'); return; }
    if (payload.provider === 'AzureOpenAI' && !payload.endpoint) { IndexLibrary.ShowToast('error', 'Endpoint is required for Azure OpenAI.'); return; }
    if (payload.authMode === 'EntraId') {
        if (!payload.tenantId) { IndexLibrary.ShowToast('error', 'Tenant ID is required for Entra ID authentication.'); return; }
        if (!payload.clientId) { IndexLibrary.ShowToast('error', 'Client ID is required for Entra ID authentication.'); return; }
    }
    try {
        const res = await IndexLibrary.PutRequest({
            endpoint: "/api/AISettings",
            body: payload
        });
        if (!res.ok) {

            IndexLibrary.ShowToast('error', 'Failed to save: ' + (t || res.status));
            return;
        }
        IndexLibrary.ShowToast('success', 'AI provider settings saved.');
        document.getElementById('aiApiKey').value = '';
        await loadAISettings();
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error saving AI settings: ' + e.message);
    }
}

async function validateByokSettings() {
    var payload = buildAiSettingsPayload();
    try {
        const res = await IndexLibrary.PostRequest({
            endpoint: "/api/AISettings/validate",
            body: payload
        });
        const data = await res.json().catch(() => null);
        if (!res.ok) { IndexLibrary.ShowToast('error', 'Validation failed: ' + ((data && data.message) || res.status)); return; }
        setAiKeyStatus('Status: ' + (data.keyStatus || ''));
        IndexLibrary.ShowToast(data.success ? 'success' : 'error', data.message || (data.success ? 'Connection successful.' : 'Validation failed.'));
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error validating connection: ' + e.message);
    }
}

async function deleteAISettings() {
    try {
        const res = await IndexLibrary.DeleteRequest({ endpoint: "/api/AISettings" });
        if (!res.ok && res.status !== 404) { IndexLibrary.ShowToast('error', 'Failed to remove settings.'); return; }
        IndexLibrary.ShowToast('success', 'AI provider settings removed. Reverting to your subscription.');
        document.getElementById('aiApiKey').value = '';
        document.getElementById('aiApiKey').setAttribute('placeholder', 'Enter your API key');
        document.getElementById('aiClientSecret').value = '';
        document.getElementById('aiClientSecret').setAttribute('placeholder', 'Enter the client secret');
        document.getElementById('aiTenantId').value = '';
        document.getElementById('aiClientId').value = '';
        document.getElementById('aiAuthMode').value = 'Key';
        onAiAuthModeChange();
        setAiKeyStatus('');
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error removing AI settings: ' + e.message);
    }
}

// ── BYOP / Presidio ──────────────────────────────────────────────────────────

const PRESIDIO_ENTITY_CLASSES = [
    { id: 'PERSON', label: 'Person names' },
    { id: 'EMAIL_ADDRESS', label: 'Email addresses' },
    { id: 'PHONE_NUMBER', label: 'Phone numbers' },
    { id: 'LOCATION', label: 'Locations' },
    { id: 'ORGANIZATION', label: 'Organizations' },
    { id: 'DATE_TIME', label: 'Dates & times' },
    { id: 'IP_ADDRESS', label: 'IP addresses' },
    { id: 'URL', label: 'URLs' },
    { id: 'CREDIT_CARD', label: 'Credit card numbers' },
    { id: 'IBAN_CODE', label: 'IBAN codes' },
    { id: 'NRP', label: 'Nationality / religious / political' },
    { id: 'US_SSN', label: 'US Social Security numbers' },
    { id: 'DOB', label: 'Date of birth' }
];

function renderPresidioEntityList() {
    var container = document.getElementById('byopEntityList');
    if (!container || container.childElementCount > 0) return;
    PRESIDIO_ENTITY_CLASSES.forEach(function (e) {
        var lbl = document.createElement('label');
        lbl.style.cssText = 'display:flex; align-items:center; gap:6px; margin:0; cursor:pointer;';
        var cb = document.createElement('input');
        cb.type = 'checkbox';
        cb.className = 'presidio-entity-cb';
        cb.value = e.id;
        cb.checked = true;
        lbl.appendChild(cb);
        lbl.appendChild(document.createTextNode(e.label));
        container.appendChild(lbl);
    });
}

function setPresidioEntitySelection(enabledEntities) {
    renderPresidioEntityList();
    var all = !Array.isArray(enabledEntities);
    document.querySelectorAll('.presidio-entity-cb').forEach(function (cb) {
        cb.checked = all || enabledEntities.indexOf(cb.value) !== -1;
    });
}

function getPresidioEntitySelection() {
    return Array.from(document.querySelectorAll('.presidio-entity-cb:checked')).map(function (cb) { return cb.value; });
}

function setAllPresidioEntities(checked) {
    document.querySelectorAll('.presidio-entity-cb').forEach(function (cb) { cb.checked = checked; });
}

function onByopPiiChange() {
    var enabled = document.getElementById('byopPiiEnabled')?.value === 'true';
    var byopFields = document.getElementById('byopFields');
    if (byopFields) byopFields.style.display = enabled ? '' : 'none';
    var entityClasses = document.getElementById('byopEntityClasses');
    if (entityClasses) {
        renderPresidioEntityList();
        entityClasses.style.display = enabled ? '' : 'none';
    }
}

function setByopTestStatus(text) {
    var el = document.getElementById('byopTestStatus');
    if (el) el.textContent = text || '';
}

async function loadPresidioSettings() {
    if (!document.getElementById('byopPiiEnabled')) return;
    try {
        const res = await IndexLibrary.GetRequest({ endpoint: "/api/AISettings/presidio" });
        if (!res.ok) return;
        const data = await res.json();
        if (!data) return;
        document.getElementById('byopPiiEnabled').value = data.usePresidio ? 'true' : 'false';
        document.getElementById('byopAnalyzerUrl').value = data.presidioAnalyzerUrl || '';
        document.getElementById('byopAnonymizerUrl').value = data.presidioAnonymizerUrl || '';
        setPresidioEntitySelection(data.enabledEntities);
        setByopTestStatus(data.status ? ('Status: ' + data.status) : '');
        onByopPiiChange();
    } catch (e) {
        console.error('Failed to load Presidio settings:', e);
    }
}

async function savePresidioSettings() {
    try {
        var analyzerUrl = (document.getElementById('byopAnalyzerUrl').value || '').trim() || null;
        var anonymizerUrl = (document.getElementById('byopAnonymizerUrl').value || '').trim() || null;

        if (!!analyzerUrl !== !!anonymizerUrl) {
            IndexLibrary.ShowToast('error', 'Both Analyzer URL and Anonymizer URL are required to use your own Presidio instance.');
            return;
        }

        var payload = {
            usePresidio: document.getElementById('byopPiiEnabled').value === 'true',
            presidioAnalyzerUrl: analyzerUrl,
            presidioAnonymizerUrl: anonymizerUrl,
            enabledEntities: getPresidioEntitySelection()
        };

        const res = await IndexLibrary.PutRequest({ endpoint: "/api/AISettings/presidio", body: payload });
        if (!res.ok) {

            IndexLibrary.ShowToast('error', 'Failed to save: ' + (t || res.status));
            return;
        }
        IndexLibrary.ShowToast('success', 'Presidio settings saved.');
        await loadPresidioSettings();
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error saving Presidio settings: ' + e.message);
    }
}

async function testPresidioSettings() {
    var analyzerUrl = (document.getElementById('byopAnalyzerUrl').value || '').trim() || null;
    var anonymizerUrl = (document.getElementById('byopAnonymizerUrl').value || '').trim() || null;

    if (!!analyzerUrl !== !!anonymizerUrl) {
        IndexLibrary.ShowToast('error', 'Both Analyzer URL and Anonymizer URL are required to test.');
        return;
    }

    var payload = {
        presidioAnalyzerUrl: analyzerUrl,
        presidioAnonymizerUrl: anonymizerUrl
    };
    try {
        const res = await IndexLibrary.PostRequest({ endpoint: "/api/AISettings/presidio/validate", body: payload });
        const data = await res.json().catch(() => null);
        if (!res.ok) { IndexLibrary.ShowToast('error', 'Test failed: ' + ((data && data.message) || res.status)); return; }
        setByopTestStatus(data.message || '');
        IndexLibrary.ShowToast(data.success ? 'success' : 'error', data.message || (data.success ? 'Connection successful.' : 'Test failed.'));
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error testing Presidio connection: ' + e.message);
    }
}

async function deletePresidioSettings() {
    try {
        const res = await IndexLibrary.DeleteRequest({ endpoint: "/api/AISettings/presidio" });
        if (!res.ok && res.status !== 404) { IndexLibrary.ShowToast('error', 'Failed to remove Presidio settings.'); return; }
        IndexLibrary.ShowToast('success', 'Presidio settings removed.');
        document.getElementById('byopPiiEnabled').value = 'false';
        document.getElementById('byopAnalyzerUrl').value = '';
        document.getElementById('byopAnonymizerUrl').value = '';
        setByopTestStatus('');
        onByopPiiChange();
    } catch (e) {
        IndexLibrary.ShowToast('error', 'Error removing Presidio settings: ' + e.message);
    }
}

function applyReleaseConfig(response, releaseNotes) {
    if (!response) return;
    var workItemFieldsMap = response.mappings;
    var releaseConfigurations = response.releaseConfigurations;

    if (releaseConfigurations) {
        $("#numberOfWorkingHours").val(releaseConfigurations.workingHoursPerDay);
        $("#numberOfWorkingDays").val(releaseConfigurations.workingDaysPerWeek);
    }

    if (!Array.isArray(workItemFieldsMap) || workItemFieldsMap.length === 0) {
        populateReleaseNotesDropdown(releaseNotes);
        return;
    }

    $("#startDateMapping").val(workItemFieldsMap.find(x => x.fieldName == 'StartDate')?.mappedField);
    $("#endDateMapping").val(workItemFieldsMap.find(x => x.fieldName == 'EndDate')?.mappedField);
    $("#effortMapping").val(workItemFieldsMap.find(x => x.fieldName == 'Effort')?.mappedField);

    const releaseNotesField = workItemFieldsMap.find(x => x.fieldName === 'ReleaseNotes');
    if (releaseNotesField) {
        const releaseNotesMappedField = releaseNotesField.mappedField;
        const updatedNotes = releaseNotes.map((note, index) => ({
            ...note,
            checked: releaseNotesMappedField.includes(note.value)
        }));
        allReleaseNotes = updatedNotes;
        populateReleaseNotesDropdown(updatedNotes);
        updateReleaseNotesDropdownLabel();
    } else {
        allReleaseNotes = releaseNotes;
        populateReleaseNotesDropdown(releaseNotes);
    }
}

function applyLicenseUI(data) {
    // ── Legacy fields (kept for backward compat) ──
    $("#userName").text(data.userName);
    $("#userEmail").text(data.userEmail);
    $("#organizationName").text(data.organizationName);
    $("#organizationInstance").text(data.organizationName);
    $("#subscriptionType").text(data.licenseType == 0 ? 'Trial' : 'Paid');
    $("#subscriptionStatus").text(data.isDeleted ? 'Canceled' : 'Active');
    $("#subscriptionID").text(data.licenseKey);
    var licenseExpiryDate = new Date(data.expiryDateTimeUtc);
    var now = new Date();
    var formattedDate = licenseExpiryDate.toLocaleDateString("en-US");
    $("#licenseExpiryDateTime").text(
        licenseExpiryDate < now ? `${formattedDate} (Expired)` : `${formattedDate} (Active)`
    );

    // ── New subscription tab elements ──
    const msLeft   = licenseExpiryDate - now;
    const daysLeft = Math.ceil(msLeft / 86400000);
    const isExpired  = msLeft <= 0;
    const isTrial    = data.licenseType === 0;
    const isExpiring = !isExpired && daysLeft <= 14;
    const planName   = isTrial ? 'Trial'
        : (data.numberOfUsers >= 50 ? 'Enterprise'
        : data.numberOfUsers >= 20 ? 'Business' : 'Pro');

    // Hero card
    const heroCard = document.getElementById('sub-hero-card');
    if (heroCard) {
        heroCard.className = 'sub-hero-card mx-3 mt-3 ' +
            (isExpired ? 'sub-hero--expired' : isExpiring ? 'sub-hero--expiring' : isTrial ? 'sub-hero--trial' : 'sub-hero--active');
    }
    const _s = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
    _s('sub-plan-name', planName);
    _s('sub-hero-org',  data.organizationName || '—');
    _s('sub-hero-user', data.userName || '—');
    _s('sub-hero-email', data.userEmail || '—');
    _s('sub-renewal-date', licenseExpiryDate.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }));
    _s('sub-days-left', isExpired
        ? `Expired ${Math.abs(daysLeft)} day${Math.abs(daysLeft) === 1 ? '' : 's'} ago`
        : `${daysLeft} day${daysLeft === 1 ? '' : 's'} remaining`);

    // Status badges (hero + account grid)
    const statusText  = isExpired ? 'Expired' : isExpiring ? 'Expiring soon' : isTrial ? 'Trial' : 'Active';
    const statusClass = `sub-status-badge sub-status--${isExpired ? 'expired' : isExpiring ? 'expiring' : isTrial ? 'trial' : 'active'}`;
    ['sub-status-badge', 'sub-status-badge2'].forEach(id => {
        const el = document.getElementById(id);
        if (el) { el.textContent = statusText; el.className = statusClass; }
    });
    _s('sub-renew-label', isExpired ? 'Renew subscription' : 'Extend / renew');

    // Alert banner
    const banner = document.getElementById('sub-alert-banner');
    if (banner) {
        if (isExpired) {
            banner.className = 'alert alert-danger d-flex align-items-center gap-2 mx-3 mt-3 mb-0';
            banner.innerHTML = `<i class="bi bi-exclamation-circle-fill"></i> Your subscription has expired. Features are unavailable until renewed. <a href="https://devopsassist.com/support-ticket/" target="_blank" class="ms-auto fw-semibold" style="color:inherit">Renew &rarr;</a>`;
            banner.style.display = '';
        } else if (isExpiring) {
            banner.className = 'alert alert-warning d-flex align-items-center gap-2 mx-3 mt-3 mb-0';
            banner.innerHTML = `<i class="bi bi-clock-history"></i> Your subscription expires in <strong>${daysLeft} day${daysLeft === 1 ? '' : 's'}</strong>. <a href="https://devopsassist.com/support-ticket/" target="_blank" class="ms-auto fw-semibold" style="color:inherit">Renew &rarr;</a>`;
            banner.style.display = '';
        } else if (isTrial) {
            banner.className = 'alert alert-warning d-flex align-items-center gap-2 mx-3 mt-3 mb-0';
            banner.innerHTML = `<i class="bi bi-exclamation-triangle-fill"></i> You are on a free 21-day trial. Upgrade before ${licenseExpiryDate.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })} to retain access. <a href="https://devopsassist.com/support-ticket/" target="_blank" class="ms-auto fw-semibold" style="color:inherit">Upgrade &rarr;</a>`;
            banner.style.display = '';
        } else {
            banner.style.display = 'none';
        }
    }

    // Account grid
    _s('sub-id',       data.licenseKey || '—');
    _s('sub-type',     planName);
    _s('sub-org',      data.organizationName || '—');
    _s('sub-instance', `dev.azure.com/${data.organizationName || ''}`);
    _s('sub-billing-email', data.userEmail || '—');
    const startDate = data.startDate ? new Date(data.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) : '—';
    _s('sub-start', startDate);
}

async function applyLicensedUsersUI(licensedUsersResponse, numberOfUsers) {
    var orgName = await IndexLibrary.GetAccountNameAsync();
    var docs = licensedUsersResponse.users;

    var licensedUsers = licensedUsersResponse.devOpsAssist?.used ?? docs.length;
    var totalAiLicenses = licensedUsersResponse.devOpsAssist?.total ?? numberOfUsers;
    var availableAiLicenses = licensedUsersResponse.devOpsAssist?.available ?? (totalAiLicenses - licensedUsers);
    totalnumberOfUsersLicense = totalAiLicenses;

    await refreshLicensedUsersList(docs, totalnumberOfUsersLicense);

    $("#numberOfLicensedUsers").text(`${licensedUsers} / ${totalAiLicenses} (${availableAiLicenses} Available)`);
    $("#numberOfTimesheetLicenses").text(`${licensedUsersResponse.timesheet.used} / ${licensedUsersResponse.timesheet.total} (${licensedUsersResponse.timesheet.available} Available)`);
    $("#numberOfCodeReviewerLicenses").text(`${licensedUsersResponse.codeReview.used} / ${licensedUsersResponse.codeReview.total} (${licensedUsersResponse.codeReview.available} Available)`);
    $("#numberOfDeveloperAgentLicenses").text(`${licensedUsersResponse.developerAgent.used} / ${licensedUsersResponse.developerAgent.total} (${licensedUsersResponse.developerAgent.available} Available)`);
    if (licensedUsersResponse.prototypeAgent) {
        $("#numberOfPrototypeAgentLicenses").text(`${licensedUsersResponse.prototypeAgent.used} / ${licensedUsersResponse.prototypeAgent.total} (${licensedUsersResponse.prototypeAgent.available} Available)`);
    }
    if (licensedUsersResponse.cleo) {
        $("#numberOfCleoLicenses").text(`${licensedUsersResponse.cleo.used} / ${licensedUsersResponse.cleo.total} (${licensedUsersResponse.cleo.available} Available)`);
    }

    // Populate subscription tab seat rows
    applySubscriptionSeats(licensedUsersResponse, totalAiLicenses);

    applyAddUserAvailability();

    $("#btnAddLicense").off('click').on('click', async function () {
        const response = await IndexLibrary.PostRequest({
            endpoint: `/api/License/AddUserLicense?organizationName=${orgName}&email=${$("#txtUserFilter").data('email')}&name=${$("#txtUserFilter").data('displayName')}&createdBy=${IndexLibrary.GetUserDisplayName()}&isDevopsAssistLicense=${isDevOpsAssistEnabled}`,
        });

        if (!response.ok) {
            const err = await response.text();
            IndexLibrary.ShowToast('error', err);
            return;
        }

        IndexLibrary.ShowToast('success', 'License Applied successfully.');
        console.log("isDevOpsAssistEnabled:", isDevOpsAssistEnabled);

        const refreshed = await IndexLibrary.GetRequest({
            endpoint: `/api/License/GetLicensedUsers?organizationName=${orgName}&IsDevopsAssistLicense=${isDevOpsAssistEnabled}`,
        }).then(r => r.json());

        await applyLicensedUsersUI(refreshed, totalnumberOfUsersLicense);
        $("#txtUserFilter").val('');
    });
}

function readSeatCounter(selector) {
    const m = $(selector).text().match(/(-?\d+)\s*\/\s*(-?\d+)\s*\((-?\d+)\s*Available\)/i);
    return m ? { used: +m[1], total: +m[2], available: +m[3] } : null;
}

function applyAddUserAvailability() {
    const ai = isDevOpsAssistEnabled ? readSeatCounter("#numberOfLicensedUsers") : null;
    const ts = isTimesheetEnabled ? readSeatCounter("#numberOfTimesheetLicenses") : null;
    if (!ai && !ts) return;

    const canAdd = (ai?.available ?? 0) > 0 || (ts?.available ?? 0) > 0;
    const products = [];
    if (isDevOpsAssistEnabled) products.push("DevOps Assist AI");
    if (isTimesheetEnabled) products.push("Timesheet");

    $("#allLicensesAllocated")
        .text(`All ${products.join(" and ")} Licenses Allocated`)
        .toggle(!canAdd);
    $("#btnAddLicense").toggle(canAdd);
    $("#txtUserFilter").closest(".licensed-users-search-box").toggle(canAdd);
    if (!canAdd) $("#userSuggestions").hide();
}

// ── Subscription tab helpers ──────────────────────────────────────────────────

function _subBarPct(used, total) {
    return total > 0 ? Math.min(100, Math.round(used / total * 100)) : 0;
}
function _subBarCls(pct) {
    return pct >= 90 ? 'danger' : pct >= 70 ? 'warning' : 'success';
}

function applySubscriptionSeats(licensedUsersResponse, numberOfUsers) {
    var seats = [
        { id: 'sub-seat-core',  used: licensedUsersResponse.devOpsAssist?.used ?? (licensedUsersResponse.users || []).length, total: numberOfUsers },
        { id: 'sub-seat-ts',    used: licensedUsersResponse.timesheet?.used   ?? 0, total: licensedUsersResponse.timesheet?.total   ?? 0 },
        { id: 'sub-seat-cr',    used: licensedUsersResponse.codeReview?.used  ?? 0, total: licensedUsersResponse.codeReview?.total  ?? 0 },
        { id: 'sub-seat-pa',    used: licensedUsersResponse.prototypeAgent?.used ?? 0, total: licensedUsersResponse.prototypeAgent?.total ?? 0 },
        { id: 'sub-seat-da',    used: licensedUsersResponse.developerAgent?.used ?? 0, total: licensedUsersResponse.developerAgent?.total ?? 0 },
        { id: 'sub-seat-cleo',  used: licensedUsersResponse.cleo?.used        ?? 0, total: licensedUsersResponse.cleo?.total        ?? 0 },
    ];
    seats.forEach(function (s) {
        var el = document.getElementById(s.id);
        if (!el) return;
        var pct = _subBarPct(s.used, s.total);
        var cls = _subBarCls(pct);
        var countEl = el.querySelector('.sub-seat-count');
        var pctEl   = el.querySelector('.sub-seat-pct');
        var barEl   = el.querySelector('.sub-seat-bar-fill');
        if (countEl) countEl.textContent = s.total > 0 ? `${s.used} / ${s.total}` : '— / —';
        if (pctEl)   { pctEl.textContent = s.total > 0 ? `${pct}%` : '—'; pctEl.style.color = pct >= 90 ? '#c4262e' : pct >= 70 ? '#ca8a00' : '#107c10'; }
        if (barEl)   { barEl.style.width = `${pct}%`; barEl.className = `sub-seat-bar-fill bg-${cls}`; }
    });
}

async function loadSubscriptionTokens() {
    try {
        var fmtK = function (n) { return (n >= 1e6) ? (n / 1e6).toFixed(1) + 'M' : (n >= 1e3) ? Math.round(n / 1e3) + 'K' : String(n || 0); };

        var aiBar = document.getElementById('sub-token-ai-bar');
        var aiNum = document.getElementById('sub-token-ai-num');
        var agBar = document.getElementById('sub-token-agent-bar');
        var agNum = document.getElementById('sub-token-agent-num');

        // Single call: pooled totals (sum of per-user MonthlyTokenLimit / MonthlyAgentTokenLimit)
        // vs actual coTokenActivity consumption for the current month.
        var res = await IndexLibrary.GetRequest({ endpoint: '/api/TokenUsage/OrgMonthlyLimits' });
        if (!res.ok) {
            console.error('[subscription] OrgMonthlyLimits returned ' + res.status);
            if (aiNum) aiNum.textContent = 'Unavailable';
            if (agNum) agNum.textContent = 'Unavailable';
            return;
        }
        var d = await res.json();

        // Interactive AI
        var aiUsed  = d.interactiveAiUsed  || 0;
        var aiTotal = d.interactiveAiTotal  || 0;
        var aiPct   = _subBarPct(aiUsed, aiTotal);
        if (aiBar) { aiBar.style.width = aiPct + '%'; aiBar.className = 'progress-bar bg-' + _subBarCls(aiPct); }
        if (aiNum) aiNum.textContent = fmtK(aiUsed) + ' / ' + fmtK(aiTotal);

        // Agent
        var agUsed  = d.agentUsed  || 0;
        var agTotal = d.agentTotal || 0;
        var agPct   = _subBarPct(agUsed, agTotal);
        if (agBar) { agBar.style.width = agPct + '%'; agBar.className = 'progress-bar bg-' + _subBarCls(agPct); }
        if (agNum) agNum.textContent = agTotal > 0 ? fmtK(agUsed) + ' / ' + fmtK(agTotal) : fmtK(agUsed) + ' used';
    } catch (e) {
        console.warn('[subscription] token load failed:', e.message);
    }
}

function copySubscriptionId() {
    var val = (document.getElementById('sub-id') || {}).textContent;
    if (val && val !== '—') {
        if (navigator.clipboard) {
            navigator.clipboard.writeText(val);
        } else {
            var ta = document.createElement('textarea');
            ta.value = val; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta);
        }
        var btn = document.getElementById('sub-copy-btn');
        if (btn) { btn.innerHTML = '<i class="bi bi-check2"></i>'; setTimeout(function () { btn.innerHTML = '<i class="bi bi-copy"></i>'; }, 1500); }
    }
}

// ─────────────────────────────────────────────────────────────────────────────

async function LoadLicenseData() {
    var orgName = await IndexLibrary.GetAccountNameAsync();
    const [licenseData, settings] = await Promise.all([
        IndexLibrary.PostRequest({
            endpoint: "/api/License/GetLicense",
            body: { organizationName: orgName }
        }).then(r => r.status === 404 ? null : r.json()).catch(() => null),

        IndexLibrary.GetRequest({
            endpoint: "/api/Settings/Get",
        }).then(r => r.json()).catch(() => null)
    ]);

    // The AI Provider (BYOK) tab is an admin entitlement — only reveal it for orgs with byokEnabled.
    if (licenseData && licenseData.byokEnabled === true) {
        $("#nav-aiprovider-tab").show();
    } else {
        $("#nav-aiprovider-tab").hide();
        $("#nav-aiprovider").removeClass("show active");
    }

    // The PII Redaction (BYOP) tab follows the same entitlement as BYOK.
    if (licenseData && licenseData.piiRedactionEnabled === true) {
        $("#nav-presidio-tab").show();
    } else {
        $("#nav-presidio-tab").hide();
        $("#nav-presidio").removeClass("show active");
    }

    if (settings) {
        $("#pageSizeForTables").val(settings.pageSizeForTables);
        const configuredPageSize = parseInt(settings.pageSizeForTables, 10);
        licensedUsersState.pageSize = Number.isInteger(configuredPageSize) && configuredPageSize > 0
            ? configuredPageSize
            : licensedUsersState.pageSize;
        if (settings.projectScope == 0) {
            $("#scopeOrganizationCheck").prop('checked', true);
            $("#scopeProjectCheck").prop('checked', false);
        } else {
            $("#scopeOrganizationCheck").prop('checked', false);
            $("#scopeProjectCheck").prop('checked', true);
        }
    }

    if (!licenseData) {
        $(".licensedContainer").hide();
        $(".nonLicensedContainer").show();
        // CreateTrialLicense();
        return;
    }

    $(".nonLicensedContainer").hide();
    $(".licensedContainer").show();
    applyLicenseUI(licenseData);
    const licensedUsersResponse = await IndexLibrary.GetRequest({
        endpoint: `/api/License/GetLicensedUsers?organizationName=${orgName}&IsDevopsAssistLicense=${isDevOpsAssistEnabled}`,
    }).then(r => r.json()).catch(() => null);

    if (licensedUsersResponse) {
        await applyLicensedUsersUI(licensedUsersResponse, licenseData.numberOfUsers);
    }
}

$(document).ready(async function () {
    // Initialize UI elements
    $("#allLicensesAllocated").hide();
    $("#btnAddLicense").hide();
    $("#userSuggestions").hide();
    $(".nonLicensedContainer").hide();
    $(".licensedContainer").hide();
    $("#noWorkTypesDefined").hide();

    $("#holidayDate").datepicker();
    $("#calendarIcon").on("click", function () {
        $("#holidayDate").datepicker("show");
    });

    $("#btnAddLicense").attr('disabled', 'disabled');

    // Run all 3 in parallel instead of one after another
    const [, tokenResult, config] = await Promise.all([
        toggleTabsBasedOnFeatures().catch(e => console.error('Features failed:', e)),
        IndexLibrary.GetAccessToken(),
        IndexLibrary.GetExtensionConfig()
    ]);
    authenticationHeader = tokenResult;
    baseAPIUrl = config.apiBaseUrl;
    extensionId = config.extensionId;
    userguidelink = config.userguidelink;
    userhelplink = config.userhelplink;
    orchestratorApiBaseUrl = config.orchestratorApiBaseUrl || '';
    orchestratorApiKey = config.orchestratorApiKey || '';

    // Load the visible tab first so content appears immediately
    await LoadLicenseData().catch(e => console.error('LicenseData failed:', e));

    // Wire subscription tab — reload token data each time the tab is shown
    // (lightweight call; re-fetch ensures data is always current and recovers from API errors)
    var subTabBtn = document.getElementById('nav-subscription-tab');
    if (subTabBtn) {
        subTabBtn.addEventListener('shown.bs.tab', function () { loadSubscriptionTokens(); });
    }

    // Wire Usage Report tab — lazy-load the 12-month trend + selected month on first/every show.
    var usageReportTabBtn = document.getElementById('nav-usagereport-tab');
    if (usageReportTabBtn) {
        usageReportTabBtn.addEventListener('shown.bs.tab', function () { loadUsageReport(); });
    }

    // Load all other tabs in the background — do NOT await so the page is usable now
    Promise.all([
        GetCodebotAIConfigurations().catch(e => console.error('CodebotAI failed:', e)),
        loadAgentContextFiles().catch(e => console.error('AgentContextFiles failed:', e)),
        loadAISettings().catch(e => console.error('AISettings failed:', e)),
        loadPresidioSettings().catch(e => console.error('PresidioSettings failed:', e)),
        PopulatePublicHolidays().catch(e => console.error('PublicHolidays failed:', e)),
        LoadTimeSheetSettings().catch(e => console.error('TimesheetSettings failed:', e)),
        LoadWorkTypes().catch(e => console.error('WorkTypes failed:', e)),
        loadWorkItemSettings().catch(e => console.error('WorkItemSettings failed:', e)),
        PopulateEvolveWorkItemTypesAndConfig().catch(e => console.error('EvolveConfig failed:', e)),
        PopulateWorkItemFieldsAndConfig().catch(e => console.error('WorkItemFieldsConfig failed:', e)),
    ]);

    $("#txtUserFilter").keyup(function () {

        $("#btnAddLicense").attr("disabled", "disabled");

        const filter = $("#txtUserFilter").val().trim().toLowerCase();

        if (!filter) {
            $("#userSuggestions").empty().hide();
            return;
        }

        const matches = Object.values(userLookup).filter(user =>
            user?.name && user?.email && user.name.toLowerCase().includes(filter)
        );

        renderUserSuggestions(matches);

        IndexLibrary.loadAvatarsFor(matches.slice(0, USER_SUGGESTION_AVATAR_LIMIT).map(user => user.email))
            .then(loaded => {
                if (loaded && $("#txtUserFilter").val().trim().toLowerCase() === filter) {
                    renderUserSuggestions(matches);
                }
            });
    });


    $(document).on('click', function (e) {
        if (!e.target.classList.contains('suggestion') && e.target.id !== 'txtUserFilter') {
            $("#userSuggestions").hide();
        }
    });

    $("#txtLicensedUsersSearch").on('input', function () {
        licensedUsersState.filterText = $(this).val().trim();
        licensedUsersState.currentPage = 1;
        renderLicensedUsersList();
    });

    $('#scopeProjectCheck').on('click', function () {
        if ($(this).is(":checked")) {
            $("#scopeOrganizationCheck").prop("checked", false);
        } else {
            $("#scopeOrganizationCheck").prop("checked", true);
        }
    });

    $('#scopeOrganizationCheck').on('click', function () {
        if ($(this).is(":checked")) {
            $("#scopeProjectCheck").prop("checked", false);
        } else {
            $("#scopeProjectCheck").prop("checked", true);
        }
    });

    const debouncedSave = debounce('pageSize', saveSettingsData, 500);
    $("#pageSizeForTables").on('input', function () {
        const newPageSize = parseInt($(this).val(), 10);
        if (Number.isInteger(newPageSize) && newPageSize > 0) {
            licensedUsersState.pageSize = newPageSize;
            licensedUsersState.currentPage = 1;
            renderLicensedUsersList();
        }
        debouncedSave();
    });

    const debouncedReleaseConfig = debounce('release', saveReleaseConfigurationsData, 500);
    $("#numberOfWorkingHours").on('change', debouncedReleaseConfig);
    $("#numberOfWorkingDays").on('change', debouncedReleaseConfig);
    $("#startDateMapping").on('change', debouncedReleaseConfig);
    $("#endDateMapping").on('change', debouncedReleaseConfig);
    $("#effortMapping").on('change', debouncedReleaseConfig);
    $(document).on('change', '.chkReleaseNote', debouncedReleaseConfig);

    const debouncedTimesheet = debounce('timesheet', saveTimesheetConfigurationsData, 500);
    $("#workLogDefaultTime").on('change', debouncedTimesheet);
    $("#chkIsBillableByDefault").on('change', debouncedTimesheet);
    $("#customerMapping").on('change', debouncedTimesheet);
    $(".quickLogButtonSelect").on('change', debouncedTimesheet);
    $("#scopeProjectCheck").on('change', saveSettingsData);
    $("#scopeOrganizationCheck").on('change', saveSettingsData);

    const debouncedCodeReviewer = debounce('codeReviewer', saveCodebotAIConfigurationsData, 500);
    $("#codebotPersonalAccessToken").on('input', debouncedCodeReviewer);
    $(document).on('change', '.review-feedback-type', debouncedCodeReviewer);

    $("#btnAddHoliday").on('click', async function () {
        const holidayDate = $("#holidayDate").val()?.trim();
        const holidayDescription = $("#holidayDescription").val()?.trim();
        const createdBy = IndexLibrary.GetUserDisplayName();

        if (!holidayDate) {
            IndexLibrary.ShowToast('error', 'Please select a holiday date.');
            return;
        }

        if (!holidayDescription) {
            IndexLibrary.ShowToast('error', 'Please enter a description.');
            return;
        }

        IndexLibrary.PostRequest({
            endpoint: `/api/Settings/InsertPublicHoliday?holidayDate=${holidayDate}&description=${holidayDescription}&createdBy=${createdBy}`,
        }).then(response => {
            return response.text();
        })
            .then((data) => {
                if (data == 'true') {
                    IndexLibrary.ShowToast('success', 'Settings saved successfully.');
                    PopulatePublicHolidays();
                    $("#holidayDate").val('');
                    $("#holidayDescription").val('');
                }
                else {
                    if (typeof data === "string") {
                        IndexLibrary.ShowToast('error', data);
                    }
                }
            })
            .catch(error => {
                IndexLibrary.ShowToast('error', 'Error saving holiday: ');
                console.error(error);
            });

    });

    // Filter logic for Release Notes
    document.getElementById('releaseNoteSearch').addEventListener('input', function () {
        const query = this.value.toLowerCase();
        const filteredNotes = allReleaseNotes.filter(note =>
            note.label.toLowerCase().includes(query)
        );
        populateReleaseNotesDropdown(filteredNotes);
    });

    // Update dropdown label on change
    document.getElementById('releaseNotesFieldMapping').addEventListener('change', () => {
        updateReleaseNotesDropdownLabel();
    });

    // Filter logic for Evolve Work Item Types
    document.getElementById('evolveWorkItemTypeSearch').addEventListener('input', function () {
        const query = this.value.toLowerCase();
        const filteredTypes = allEvolveWorkItemTypes.filter(wit =>
            wit.label.toLowerCase().includes(query)
        );
        populateEvolveWorkItemTypesDropdown(filteredTypes);
    });

    // Update dropdown label on change for Evolve
    document.getElementById('evolveWorkItemTypeMapping').addEventListener('change', () => {
        updateEvolveWorkItemTypesDropdownLabel();
    });

    // Filter logic for Summarize Fields
    document.getElementById('summarizeFieldsSearch').addEventListener('input', function () {
        const query = this.value.toLowerCase();
        const filteredFields = allSummarizeFields.filter(field =>
            field.label.toLowerCase().includes(query)
        );
        populateSummarizeFieldsDropdown(filteredFields);
    });

    // Update dropdown label on change for Summarize
    document.getElementById('summarizeFieldsMapping').addEventListener('change', () => {
        updateSummarizeFieldsDropdownLabel();
    });

})



// This function populates the release notes dropdown with checkboxes
function populateReleaseNotesDropdown(notes) {
    const container = document.getElementById('releaseNoteCheckboxContainer');
    if (!container) {
        console.warn('Dropdown container not found');
        return;
    }
    container.innerHTML = ''; // Clear any existing options

    notes.forEach(note => {
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'form-check-input chkReleaseNote';
        checkbox.id = note.id;
        checkbox.dataset.releaseNote = note.value;
        checkbox.dataset.releaseNoteText = note.label;
        checkbox.checked = note.checked;

        const debouncedRelease = debounce('release', saveReleaseConfigurationsData, 500);
        checkbox.addEventListener('change', function () {
            const index = allReleaseNotes.findIndex(n => n.id === note.id);
            if (index !== -1) {
                allReleaseNotes[index].checked = this.checked;
            }
            debouncedRelease();
        });

        const label = document.createElement('label');
        label.className = 'form-check-label';
        label.setAttribute('for', note.id);
        label.innerText = note.label;
        label.style.marginLeft = '8px';

        const div = document.createElement('div');
        div.className = 'form-check';
        div.appendChild(checkbox);
        div.appendChild(label);
        container.appendChild(div);
    });
}

// This function updates the dropdown button label based on selected release notes
function updateReleaseNotesDropdownLabel() {
    const selectedValues = getSelectedReleaseNotes();
    const button = document.getElementById('dropdownWorkItemTypeFilter');
    button.innerText = selectedValues.length
        ? selectedValues.join(', ')
        : '<-- No Mapping -->';
}

// This function retrieves the selected release notes from the global allReleaseNotes array
function getSelectedReleaseNotes() {
    return allReleaseNotes
        .filter(note => note.checked)
        .map(note => note.label);
}

// This function populates the Evolve work item types dropdown with checkboxes
function populateEvolveWorkItemTypesDropdown(workItemTypes) {
    const container = document.getElementById('evolveWorkItemTypeCheckboxContainer');
    if (!container) {
        console.warn('Evolve work item type dropdown container not found');
        return;
    }
    container.innerHTML = ''; // Clear any existing options

    workItemTypes.forEach(wit => {
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'form-check-input chkEvolveWorkItemType';
        checkbox.id = wit.id;
        checkbox.dataset.workItemType = wit.value;
        checkbox.dataset.workItemTypeText = wit.label;
        checkbox.checked = wit.checked;

        const debouncedSaveEvolve = debounce('evolve', saveEvolveConfigurationsData, 500);
        checkbox.addEventListener('change', function () {
            const index = allEvolveWorkItemTypes.findIndex(w => w.id === wit.id);
            if (index !== -1) {
                allEvolveWorkItemTypes[index].checked = this.checked;
            }
            debouncedSaveEvolve();
        });

        const label = document.createElement('label');
        label.className = 'form-check-label';
        label.setAttribute('for', wit.id);
        label.innerText = wit.label;
        label.style.marginLeft = '8px';

        const div = document.createElement('div');
        div.className = 'form-check';
        div.appendChild(checkbox);
        div.appendChild(label);
        container.appendChild(div);
    });
}

// This function updates the dropdown button label based on selected work item types
function updateEvolveWorkItemTypesDropdownLabel() {
    const selectedValues = getSelectedEvolveWorkItemTypes();
    const button = document.getElementById('dropdownEvolveWorkItemTypeFilter');
    button.innerText = selectedValues.length
        ? selectedValues.join(', ')
        : '<-- No Selection -->';
}

// This function retrieves the selected work item types from the global allEvolveWorkItemTypes array
function getSelectedEvolveWorkItemTypes() {
    return allEvolveWorkItemTypes
        .filter(wit => wit.checked)
        .map(wit => wit.label);
}

// This function populates the Summarize fields dropdown with checkboxes
function populateSummarizeFieldsDropdown(fields) {
    const container = document.getElementById('summarizeFieldsCheckboxContainer');
    if (!container) {
        console.warn('Summarize fields dropdown container not found');
        return;
    }
    container.innerHTML = '';

    fields.forEach(field => {
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'form-check-input chkSummarizeField';
        checkbox.id = field.id;
        checkbox.dataset.summarizeField = field.value;
        checkbox.dataset.summarizeFieldText = field.label;
        checkbox.checked = field.checked;
        const debouncedSaveSummarize = debounce('summarize', saveSummarizeConfigurationsData, 500);
        checkbox.addEventListener('change', function () {
            const index = allSummarizeFields.findIndex(f => f.id === field.id);
            if (index !== -1) {
                allSummarizeFields[index].checked = this.checked;
            }
            debouncedSaveSummarize();
        });

        const label = document.createElement('label');
        label.className = 'form-check-label';
        label.setAttribute('for', field.id);
        label.innerText = field.label;
        label.style.marginLeft = '8px';

        const div = document.createElement('div');
        div.className = 'form-check';
        div.appendChild(checkbox);
        div.appendChild(label);
        container.appendChild(div);
    });
}

// This function updates the dropdown button label based on selected fields
function updateSummarizeFieldsDropdownLabel() {
    const selectedValues = getSelectedSummarizeFields();
    const button = document.getElementById('dropdownSummarizeFieldsFilter');
    button.innerText = selectedValues.length
        ? selectedValues.join(', ')
        : '<-- No Mapping -->';
}

// This function retrieves the selected fields from the global allSummarizeFields array
function getSelectedSummarizeFields() {
    return allSummarizeFields
        .filter(field => field.checked)
        .map(field => field.label);
}


// Start - Users table and details panel related functions

function getLicensedUserDisplayName(doc) {
    const emailKey = doc.userEmail?.toLowerCase();
    const user = userLookup[emailKey];
    return user && user.name?.trim() ? user.name.trim() : (doc.userEmail?.trim() || '');
}

async function getLicensedUsersPageStorageKey() {
    const orgName = await IndexLibrary.GetAccountNameAsync();
    return `devopsAssist.licensedUsersPage.${orgName}`;
}

async function getStoredLicensedUsersPage() {
    try {
        const key = await getLicensedUsersPageStorageKey();
        const stored = parseInt(localStorage.getItem(key), 10);
        return Number.isInteger(stored) && stored > 0 ? stored : 1;
    } catch (e) {
        return 1;
    }
}

async function persistLicensedUsersPage(page) {
    try {
        const key = await getLicensedUsersPageStorageKey();
        localStorage.setItem(key, String(page));
    } catch (e) {
    }
}

async function refreshLicensedUsersList(docs, numberOfUsers) {
    allUsers = await IndexLibrary.getAllUsers();
    await IndexLibrary.loadAvatarsFor((docs || []).map(doc => doc.userEmail));
    userLookup = IndexLibrary.buildUserLookup(allUsers);
    if (allUsers && allUsers.length) {
        for (const user of allUsers) {
            if (user.email) userLookup[user.email.toLowerCase()] = user;
        }
    }

    licensedUsersState.allDocs = docs || [];
    licensedUsersState.numberOfUsers = numberOfUsers;

    if (!licensedUsersListInitialized) {
        licensedUsersListInitialized = true;
        licensedUsersState.currentPage = await getStoredLicensedUsersPage();
    } else {
        licensedUsersState.currentPage = 1;
    }

    renderLicensedUsersList();
}

function renderLicensedUsersList() {
    const { allDocs, numberOfUsers, filterText, pageSize } = licensedUsersState;

    let filtered = allDocs.slice().sort((a, b) =>
        getLicensedUserDisplayName(a).localeCompare(getLicensedUserDisplayName(b), undefined, { sensitivity: 'base' })
    );

    if (filterText) {
        const needle = filterText.toLowerCase();
        filtered = filtered.filter(doc =>
            getLicensedUserDisplayName(doc).toLowerCase().includes(needle) ||
            (doc.userEmail || '').toLowerCase().includes(needle)
        );
    }

    const totalItems = filtered.length;
    const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
    if (licensedUsersState.currentPage > totalPages) licensedUsersState.currentPage = totalPages;
    if (licensedUsersState.currentPage < 1) licensedUsersState.currentPage = 1;
    persistLicensedUsersPage(licensedUsersState.currentPage);

    const startIdx = (licensedUsersState.currentPage - 1) * pageSize;
    const pageDocs = filtered.slice(startIdx, startIdx + pageSize);

    $(".licensed-users-container").html(GetlicensedUsersHtml(pageDocs, numberOfUsers, totalItems));
    renderLicensedUsersPagination(totalItems, totalPages);
}

function renderLicensedUsersPagination(totalItems, totalPages) {
    const container = $("#licensedUsersPagination");
    if (!container.length) return;

    if (totalItems === 0) {
        container.html('');
        return;
    }

    const cur = licensedUsersState.currentPage;
    container.html(`
        <div class="pagination-controls">
            <button type="button" class="btn" id="btnLicensedUsersPrev" ${cur <= 1 ? 'disabled' : ''}>&laquo; Prev</button>
            <span class="pagination-info">Page ${cur} of ${totalPages} (${totalItems} user${totalItems === 1 ? '' : 's'})</span>
            <button type="button" class="btn" id="btnLicensedUsersNext" ${cur >= totalPages ? 'disabled' : ''}>Next &raquo;</button>
        </div>
    `);

    $("#btnLicensedUsersPrev").off('click').on('click', function () {
        if (licensedUsersState.currentPage > 1) {
            licensedUsersState.currentPage--;
            renderLicensedUsersList();
        }
    });
    $("#btnLicensedUsersNext").off('click').on('click', function () {
        licensedUsersState.currentPage++;
        renderLicensedUsersList();
    });
}

function GetlicensedUsersHtml(docs, numberOfUsers, totalItems) {

    if (!docs.length) {
        return `<div class="no-licensed-users">${totalItems ? 'No users match your search.' : 'No licensed users yet.'}</div>`;
    }

    let html = `
        <table id="licensedUsersTable" style="width:100%">
            <thead>
                <tr>
                    <th class="text-start">Avatar</th>
                    <th class="text-start">Name</th>
                    <th class="text-end">Action</th>
                </tr>
            </thead>
            <tbody>
    `;

    for (let doc of docs) {
        const { id, userEmail } = doc;
        const json = JSON.stringify(doc).replace(/"/g, '&quot;');
        const emailKey = userEmail?.toLowerCase();
        const user = userLookup[emailKey];
        const userName = user && user.name?.trim() ? user.name.trim() : userEmail?.trim() || '';
        const avatarLink = user?.avatar || "";
        const tooltipText = (userName.toLowerCase() !== userEmail.toLowerCase())
            ? `${userName.replace(/'/g, "&#39;")}\n${userEmail.replace(/'/g, "&#39;")}`
            : userName.replace(/'/g, "&#39;");

        const avatarHtml = avatarLink
            ? `<img src="${avatarLink}" class="avatar-img" alt="Avatar" title="${tooltipText}" />`
            : `<span class="avatar" title="${tooltipText}">${IndexLibrary.getInitials(userEmail)}</span>`;

        html += `
            <tr onclick="showUserDetails(this, JSON.parse('${json}'))">
                <td class="licensed-user-avatar-cell">${avatarHtml}</td>
                <td class="licensed-user-name-cell" title="${tooltipText}">${userName}</td>
                <td>
                    <button
                        class="btn btn-primary btnRevokeLicense"
                        style="--bs-btn-padding-y:0; font-size:12px; background-color:#0078d4;"
                        data-doc-id="${id}"
                        onclick="RevokeLicense('${id}', '${userEmail}', '${numberOfUsers}')"
                    >
                        <i
                            class="bi bi-trash"
                            style="color:white; padding-right:8px;"
                        ></i>
                        Remove
                    </button>
                </td>
            </tr>
        `;
    }
    html += `
            </tbody>
        </table>
    `;
    return html;
}

const USER_SUGGESTION_AVATAR_LIMIT = 50;

function renderUserSuggestions(users) {
    let html = `
        <table id="userSuggestionsTable" style="width:100%">
            <thead>
                <tr>
                    <th class="text-start">Avatar</th>
                    <th class="text-start">Name</th>
                    <th class="text-start">Email</th>
                </tr>
            </thead>
            <tbody>
    `;

    users.forEach(user => {
        const userName = user.name.trim();
        const userEmail = user.email.trim();
        const avatarLink = IndexLibrary.getCachedAvatar(userEmail);

        const avatarHtml = avatarLink
            ? `<img src="${avatarLink}" class="avatar-img" title="${userName}" />`
            : `<span class="avatar" title="${userName}">${IndexLibrary.getInitials(userName)}</span>`;

        html += `
        <tr style="cursor:pointer"
            onclick="SelectUserFromSuggestion(this)"
            data-display-name="${userName}"
            data-email="${userEmail}">
            <td class="licensed-user-avatar-cell">${avatarHtml}</td>
            <td title="${userName}">${userName}</td>
            <td title="${userEmail}">${userEmail}</td>
        </tr>
        `;
    });

    html += `</tbody></table>`;

    $("#userSuggestions").html(html).show();
}

function showUserDetails(row, user) {
    // Remove active from any other row
    const table = row.closest('#licensedUsersTable');
    table.querySelectorAll('tr.active').forEach(r => r.classList.remove('active'));

    // Add active to clicked row
    row.classList.add('active');
    // Mapping function
    const getSelected = (value, optionValue) => {

        if (value === 0 && optionValue === 'Standard') return 'selected';
        if (value === 1 && optionValue === 'Manager') return 'selected';
        return '';
    };
    const emailKey = (user.userEmail || user.email || '').toLowerCase();
    const userlookup = userLookup[emailKey] || user; // fallback to doc if lookup fails

    let avatarHtml = '';
    if (userlookup.avatar) {

        avatarHtml = `<img src="${witSafeUrl(userlookup.avatar)}" class="avatar-img" alt="Avatar" style="width:32px;height:32px;border-radius:50%;">`;
    } else {

        const initials = IndexLibrary.getInitials(userlookup.userEmail || userlookup.email || '');
        avatarHtml = `<span class="avatar" style="width:32px;height:32px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center">
                        ${witEsc(initials)}
                      </span>`;
    }
    function getAiTokensRowHtml(userEmail) {
        return `
            <div class="perm-row" id="aiTokensRow">
                <label>AI Tokens:</label>
                <div style="display: flex; align-items: center; gap: 5px;">
                    <span id="numberofOpenAITokens">Loading...</span>
                    <a href="#" id="btnAddMoreOpenAITokens" onclick="AddMoreOpenAITokens(${witEscJs(userEmail)})" style="font-size:13px;">
                        Add More
                    </a>
                </div>
            </div>`;
    }
    function getAgentTokensRowHtml(userEmail) {
        return `
            <div class="perm-row" id="agentTokensRow">
                <label>Agent Tokens:</label>
                <div style="display: flex; align-items: center; gap: 6px;">
                    <span id="numberofAgentAITokens">Loading...</span>
                    <a href="#"
                    id="btnAddMoreAgentTokens"
                    onclick="AddMoreAgentTokens(${witEscJs(userEmail)})"
                    style="font-size:13px;">
                        Add More
                    </a>
                </div>
            </div>`;
    }
    const showPermissionsSection = isEvolveEnabled || isGroomEnabled || isSummaryEnabled || isReleaseEnabled || isDevOpsAssistEnabled;
    const showAddOnsSection = isTimesheetEnabled || isCodeReviewerEnabled || isDeveloperAgentEnabled || isCleoEnabled;
    const aiUserType = user.eaUserType ?? user.gaUserType ?? user.tsUserType ?? user.rmUserType ?? null;
    const showAiTokens = showPermissionsSection && aiUserType != null;
    const showAgentModulesSection = isCodeReviewerEnabled || isDeveloperAgentEnabled || isCleoEnabled;
    document.getElementById('userDetailsContainer').innerHTML = `
        <div class="userdetailcard">
            <button class="bi bi-x-lg close-btn" onclick="closeUserDetails()"></button>
            <div class="section">
                <h4>User Details</h4>
                <div class="perm-row" style="display: flex; justify-content: space-between; align-items: center;">
                <strong>Name:</strong>
                <div style="display: flex; align-items: center; gap: 4px;">
                    ${avatarHtml}
                    <span>${witEsc(user.name || user.userName || user.userEmail || '')}</span>
                </div>
                </div>
                <div class="perm-row"><label><strong>Email:</strong></label><span>${witEsc(user.userEmail || user.email || '')}</span></div>
                <div class="perm-row"><label><strong>Status:</strong></label><span>Active</span></div>
            </div>
            <br /><br />

            ${showPermissionsSection ? `
            <div class="section">
                <h4>DevOps Assist AI</h4>
               ${showAiTokens ? getAiTokensRowHtml(user.userEmail || user.email) : ''}
            ${createPermissionRow('DevOps Assist AI', 'devOpsAssist', aiUserType)}
            </div>
            <br /><br />
            ` : ''}

            ${isTimesheetEnabled ? `
            <div class="section">
                <h4>Timesheets</h4>
                ${createPermissionRow('Timesheets', 'ptUserType', user.ptUserType)}
            </div>
            <br /><br />
            ` : ''}

            ${showAgentModulesSection ? `
            <div class="section">
                <h4>Agent Modules</h4>
                ${((isCodeReviewerEnabled && user.crUserType != null) || (isDeveloperAgentEnabled && (user.daUserType != null || user.paUserType != null)) || (isCleoEnabled && user.clUserType != null)) ? getAgentTokensRowHtml(user.userEmail || user.email) : ''}
                ${isCodeReviewerEnabled ? createPermissionRow('Code Reviewer', 'crUserType', user.crUserType) : ''}
                ${isDeveloperAgentEnabled ? createPermissionRow('Developer Agent', 'daUserType', user.daUserType) : ''}
                ${isDeveloperAgentEnabled ? createPermissionRow('Prototype Agent', 'paUserType', user.paUserType) : ''}
                ${isCleoEnabled ? createPermissionRow('Cleo Agent', 'clUserType', user.clUserType) : ''}
            </div>
            
            ` : ''}

        </div>
    `;
    if (showAiTokens) LoadMonthlyTokenUsage(user.userEmail);
    if (isCodeReviewerEnabled || isDeveloperAgentEnabled || isCleoEnabled) LoadMonthlyAgentTokenUsage(user.userEmail);

    // Attach event listeners to all dropdowns
    document.querySelectorAll('.dropdown').forEach(dropdown => {
        dropdown.addEventListener('change', async (e) => {
            // Get selected value and field
            const field = e.target.getAttribute('data-field');
            const value = e.target.value;

            // Prepare request object
            const payload = {
                userId: user.id,
                fieldName: field,
                fieldValue: value === 'Manager' ? 1 : value === 'Standard' ? 0 : null,
                updatedBy: IndexLibrary.GetUserDisplayName()
            };


            IndexLibrary.PostRequest({
                endpoint: "/api/License/UpdateUserLicense",
                body: payload
            })
                .then(async response => response.json())
                .then(data => {
                    if (!data.success) {
                        IndexLibrary.ShowToast('error', data.message);
                        return;
                    }

                    IndexLibrary.ShowToast('success', 'License updated successfully.');
                    if (field === 'devOpsAssist') {
                        const aiEl = document.querySelector('[data-field="devOpsAssist"]');
                        if (aiEl && aiEl.value === 'NotSet') {
                            $("#aiTokensRow").remove();
                        } else if (aiEl && $("#aiTokensRow").length === 0) {
                            $(aiEl).closest(".perm-row").before(getAiTokensRowHtml(user.userEmail || user.email));
                            LoadMonthlyTokenUsage(user.userEmail || user.email);
                        }
                    }
                    if (data.devOpsAssist) {
                        $("#numberOfLicensedUsers").text(
                            `${data.devOpsAssist.used} / ${data.devOpsAssist.total} (${data.devOpsAssist.available} Available)`
                        );
                    }
                    if (field === 'crUserType' || field === 'daUserType' || field === 'paUserType' || field === 'clUserType') {
                        const crEl = document.querySelector('[data-field="crUserType"]');
                        const daEl = document.querySelector('[data-field="daUserType"]');
                        const paEl = document.querySelector('[data-field="paUserType"]');
                        const clEl = document.querySelector('[data-field="clUserType"]');
                        const crValue = crEl ? (crEl.value === 'NotSet' ? null : crEl.value) : null;
                        const daValue = daEl ? (daEl.value === 'NotSet' ? null : daEl.value) : null;
                        const paValue = paEl ? (paEl.value === 'NotSet' ? null : paEl.value) : null;
                        const clValue = clEl ? (clEl.value === 'NotSet' ? null : clEl.value) : null;
                        const allNoLicense = crValue === null && daValue === null && paValue === null && clValue === null;

                        if (allNoLicense) {
                            $("#agentTokensRow").remove();
                        } else if ($("#agentTokensRow").length === 0) {
                            const anchorEl = document.querySelector('[data-field="crUserType"]') || document.querySelector('[data-field="daUserType"]') || document.querySelector('[data-field="paUserType"]') || document.querySelector('[data-field="clUserType"]');
                            $(anchorEl).closest(".perm-row").before(getAgentTokensRowHtml(user.userEmail || user.email));
                            LoadMonthlyAgentTokenUsage(user.userEmail || user.email);
                        }
                    }
                    $("#numberOfTimesheetLicenses").text(
                        `${data.timesheet.used} / ${data.timesheet.total} (${data.timesheet.available} Available)`
                    );
                    $("#numberOfCodeReviewerLicenses").text(
                        `${data.codeReview.used} / ${data.codeReview.total} (${data.codeReview.available} Available)`
                    );
                    $("#numberOfDeveloperAgentLicenses").text(
                        `${data.developerAgent.used} / ${data.developerAgent.total} (${data.developerAgent.available} Available)`
                    );
                    if (data.prototypeAgent) {
                        $("#numberOfPrototypeAgentLicenses").text(
                            `${data.prototypeAgent.used} / ${data.prototypeAgent.total} (${data.prototypeAgent.available} Available)`
                        );
                    }
                    if (data.cleo) {
                        $("#numberOfCleoLicenses").text(
                            `${data.cleo.used} / ${data.cleo.total} (${data.cleo.available} Available)`
                        );
                    }
                    applyAddUserAvailability();
                });

        });
    });

    function createPermissionRow(label, field, currentValue) {
        const allowManager = field !== 'crUserType' && field !== 'daUserType' && field !== 'paUserType' && field !== 'clUserType';
        return `
      <div class="perm-row">
        <label>${label}</label>
        <select class="dropdown" data-field="${field}">
            <option value="Standard" ${currentValue === 0 ? 'selected' : ''}>
                Standard
            </option>

            ${allowManager
                ? `<option value="Manager" ${currentValue === 1 ? 'selected' : ''}>
                        Manager
                   </option>`
                : ''
            }

            <option value="NotSet" ${currentValue === null ? 'selected' : ''}>
                No License
            </option>
        </select>
      </div>
    `;
    }
}

function closeUserDetails() {
    // Remove active class from any row
    const table = document.getElementById('licensedUsersTable');
    if (table) {
        table.querySelectorAll('tr.active').forEach(r => r.classList.remove('active'));
    }

    document.getElementById('userDetailsContainer').innerHTML =
        `<div class="no-user">Select a user to view details.</div>`;
}

async function RevokeLicense(id, customerEmail, numberOfUsers) {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.DeleteRequest({
        endpoint: `/api/License/DeleteUserLicense?organizationName=${organizationName}&email=${customerEmail}&deletedBy=${IndexLibrary.GetUserDisplayName()}`,
    })
        .then(function (deleteLicenseResponse) {

            IndexLibrary.GetRequest({
                endpoint: `/api/License/GetLicensedUsers?organizationName=${organizationName}&IsDevopsAssistLicense=${isDevOpsAssistEnabled}`,
            })
                .then((response) => response.json())
                .then(async function (licensedUsersResponse) {
                    var docs = licensedUsersResponse.users;
                    await refreshLicensedUsersList(docs, totalnumberOfUsersLicense);
                    var licensedUsers = licensedUsersResponse.devOpsAssist?.used ?? docs.length;
                    var totalLicenses = licensedUsersResponse.devOpsAssist?.total ?? totalnumberOfUsersLicense;
                    $("#numberOfLicensedUsers").text(`${licensedUsers} / ${totalLicenses} (${totalLicenses - licensedUsers} Available)`);
                    $("#numberOfTimesheetLicenses").text(
                        `${licensedUsersResponse.timesheet.used} / ${licensedUsersResponse.timesheet.total} (${licensedUsersResponse.timesheet.available} Available)`
                    );
                    $("#numberOfCodeReviewerLicenses").text(
                        `${licensedUsersResponse.codeReview.used} / ${licensedUsersResponse.codeReview.total} (${licensedUsersResponse.codeReview.available} Available)`
                    );
                    $("#numberOfDeveloperAgentLicenses").text(
                        `${licensedUsersResponse.developerAgent.used} / ${licensedUsersResponse.developerAgent.total} (${licensedUsersResponse.developerAgent.available} Available)`
                    );
                    if (licensedUsersResponse.prototypeAgent) {
                        $("#numberOfPrototypeAgentLicenses").text(
                            `${licensedUsersResponse.prototypeAgent.used} / ${licensedUsersResponse.prototypeAgent.total} (${licensedUsersResponse.prototypeAgent.available} Available)`
                        );
                    }
                    if (licensedUsersResponse.cleo) {
                        $("#numberOfCleoLicenses").text(
                            `${licensedUsersResponse.cleo.used} / ${licensedUsersResponse.cleo.total} (${licensedUsersResponse.cleo.available} Available)`
                        );
                    }
                    applyAddUserAvailability();

                    closeUserDetails();
                    IndexLibrary.ShowToast('success', 'License revoked successfully.');

                });
        });

}

// End - Users table and details panel related functions

function SaveTimesheetConfigurations() {
    saveTimesheetConfigurationsData();
}

async function LoadTimeSheetSettings() {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    populateQuickLogButtonSelects();

    return IndexLibrary.GetRequest({
        endpoint: `/api/TimesheetSettings/GetSettings/${organizationName}`,
    })
        .then((response) => response.json())
        .then(function (json) {
            $("#workLogDefaultTime").val(json.defaultWorkLogTime);
            $("#chkIsBillableByDefault").prop("checked", json.allWorkLogsBillableByDefault);
            setQuickLogButtonMinutesOnForm(json.quickLogButtonMinutes);
        })
}

async function LoadWorkTypes() {
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    return IndexLibrary.GetRequest({
        endpoint: `/api/TrackingWorkType/GetAll/${organizationName}`,
    })
        .then((response) => response.json())
        .then(function (json) {
            if (json.length == 0) {
                $("#noWorkTypesDefined").show();
            } else {

                let html = `
                    <table id='workTypesTable' style='width:100%; border-collapse: collapse;'>
                        <thead>
                            <tr>
                                <th>Color</th>
                                <th>Work Type</th>
                                <th>Is Default</th>
                                <th>Effort Included</th>
                                <th></th>
                                <th></th>
                            </tr>
                        </thead>
                        <tbody>
                `;
                for (let i = 0; i < json.length; i++) {
                    const wt = json[i];
                    if (!wt.isDeleted) {
                        html += `
                            <tr>
                                <td><span style="display:inline-block; width:20px; height:20px; background-color:${sanitizeCssColor(wt.trackingWorkTypeColor)};"></span></td>
                                <td>${escapeHtml(wt.trackingWorkTypeTitle)}</td>
                                <td>${escapeHtml(wt.isDefault)}</td>
                                <td>${escapeHtml(wt.isEffortIncluded)}</td>
                                <td><a href="#" class="editWorkType"
                                    data-worktype-id="${escapeHtml(wt.id)}"
                                    data-worktype-title="${escapeHtml(wt.trackingWorkTypeTitle)}"
                                    data-worktype-default="${escapeHtml(wt.isDefault)}"
                                    data-worktype-effort="${escapeHtml(wt.isEffortIncluded)}"
                                    data-worktype-color="${escapeHtml(wt.trackingWorkTypeColor)}">Edit</a></td>
                                <td><a href="#" class="deleteWorkType" data-worktype-id="${escapeHtml(wt.id)}">Delete</a></td>
                            </tr>
                        `;
                    }
                }

                html += `
                        </tbody>
                    </table>
                `;

                $("#worktypesgrid").html(html);

                $("#worktypesgrid").off('click', '.editWorkType').on('click', '.editWorkType', function (e) {
                    e.preventDefault();
                    const link = $(this);
                    EditWorkType(
                        link.data('worktypeId'),
                        link.data('worktypeTitle'),
                        link.data('worktypeDefault'),
                        link.data('worktypeColor'),
                        link.data('worktypeEffort')
                    );
                });

                $("#worktypesgrid").off('click', '.deleteWorkType').on('click', '.deleteWorkType', function (e) {
                    e.preventDefault();
                    DeleteWorkType($(this).data('worktypeId'));
                });
            }
        });
}


async function LoadMonthlyTokenUsage(userEmail) {
    const el = document.getElementById("numberofOpenAITokens");
    if (!el) return;
    const organizationName = await IndexLibrary.GetAccountNameAsync();

    IndexLibrary.GetRequest({
        endpoint: `/api/TokenUsage/CurrentMonthlyToken?userEmail=${encodeURIComponent(userEmail)}`,
    })
        .then(response => {
            if (!response.ok) {
                throw new Error("Failed to fetch token usage");
            }
            return response.json();
        })
        .then(data => {
            const used = data.usedTokensThisMonth;
            const total = data.totalTokens;
            const remaining = data.remainingTokens;
            const metered = Math.max(used - total, 0);
            $("#numberofOpenAITokens").text(
                `${used.toLocaleString()} / ${total.toLocaleString()} (${remaining.toLocaleString()} Available)- Metered (${metered.toLocaleString()})`
            );
        })
        .catch(err => {
            console.error("Token usage load failed:", err);
            $("#numberofOpenAITokens").text("—");
        });
}
async function LoadMonthlyAgentTokenUsage(userEmail) {
    const el = document.getElementById("numberofAgentAITokens");
    if (!el) return;
    const organizationName = await IndexLibrary.GetAccountNameAsync();


    IndexLibrary.GetRequest({
        endpoint: `/api/TokenUsage/CurrentMonthlyAgentToken?userEmail=${encodeURIComponent(userEmail)}`,
    })
        .then(response => {
            if (!response.ok) {
                throw new Error("Failed to fetch token usage");
            }
            return response.json();
        })
        .then(data => {
            const used = data.usedTokensThisMonth;
            const total = data.totalTokens;
            const remaining = data.remainingTokens;
            const metered = Math.max(used - total, 0);
            $("#numberofAgentAITokens").text(
                `${used.toLocaleString()} / ${total.toLocaleString()} (${remaining.toLocaleString()} Available)- Metered (${metered.toLocaleString()})`
            );
        })
        .catch(err => {
            console.error("Token usage load failed:", err);
            $("#numberofAgentAITokens").text("—");
        });
}

// ==========================================================================
// Usage Report tab (permissions.html #nav-usagereport)
// KPI cards + 12-month trend + per-module breakdown + CSV export, over the
// existing /api/TokenUsage/MonthlyTrend and /api/TokenUsage/MonthlyReport
// endpoints (org auto-scoped via IndexLibrary's OrganizationName header).
// Per-user breakdown (user filter, per-user table, per-user CSV) is enabled here.
// NOTE: MonthlyReport still returns UserBreakdown to any caller — the server-side VR-3
// Manager-gating (omit UserBreakdown for non-Manager callers) remains a recommended
// defense-in-depth follow-up. This page already lists all licensed users, so surfacing
// their token usage here is consistent with the page's existing data exposure.
// ==========================================================================
const UR_SHOW_USER_BREAKDOWN = true;

let _urTrend = null;      // last MonthlyTrend response (chronological ascending)
let _urReport = null;     // last MonthlyReport response for the selected month
let _urOrgName = '';
let _urModule = '';       // module filter (source module key; '' = all)
let _urUser = '';         // user filter (email; '' = all) — only used when UR_SHOW_USER_BREAKDOWN
let _urFiltersPopulated = false;

function urFmt(n) { return Number(n || 0).toLocaleString(); }
function urModuleColor(group) { return group === 'agent' ? '#5c2d91' : '#0078d4'; }
function urCsvCell(v) {
    const s = String(v == null ? '' : v);
    return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
}
function urQuery(base) {
    const p = [];
    if (_urModule) p.push('module=' + encodeURIComponent(_urModule));
    if (UR_SHOW_USER_BREAKDOWN && _urUser) p.push('userEmail=' + encodeURIComponent(_urUser));
    return p.length ? base + (base.indexOf('?') >= 0 ? '&' : '?') + p.join('&') : base;
}

async function loadUsageReport() {
    try {
        _urOrgName = await IndexLibrary.GetAccountNameAsync();
        await LoadUsageReportTrend();
    } catch (err) {
        console.error('Usage Report load failed:', err);
    }
}

async function LoadUsageReportTrend(populateMonths = true) {
    const resp = await IndexLibrary.GetRequest({ endpoint: urQuery('/api/TokenUsage/MonthlyTrend?monthsBack=12') });
    if (!resp.ok) throw new Error('MonthlyTrend fetch failed');
    _urTrend = await resp.json();

    renderUsageReportTrend(_urTrend);

    // Month dropdown: descending (current month first), value = "yyyy-MM". Populated on first load
    // only — filter refreshes pass populateMonths=false so the selected month is preserved.
    const sel = document.getElementById('ur-month-select');
    if (sel && populateMonths) {
        const opts = [];
        for (let i = _urTrend.length - 1; i >= 0; i--) {
            opts.push(`<option value="${_urTrend[i].month}">${_urTrend[i].monthLabel || _urTrend[i].month}</option>`);
        }
        sel.innerHTML = opts.join('');
        if (_urTrend.length) {
            sel.value = _urTrend[_urTrend.length - 1].month; // default to current month
            await LoadUsageReportMonth(sel.value);
        }
    }
}

async function LoadUsageReportMonth(month) {
    if (!month) return;
    const resp = await IndexLibrary.GetRequest({ endpoint: urQuery(`/api/TokenUsage/MonthlyReport?month=${encodeURIComponent(month)}`) });
    if (!resp.ok) throw new Error('MonthlyReport fetch failed');
    _urReport = await resp.json();
    populateUsageReportFilters(_urReport);
    renderUsageReportMonth(_urReport);
}

// Module list is populated once (stable across months). The user list is refreshed on every
// unfiltered month load so it always reflects the users active in the selected month/module.
function populateUsageReportFilters(report) {
    const modSel = document.getElementById('ur-module-filter');
    if (modSel && !_urFiltersPopulated && Array.isArray(report.moduleBreakdown)) {
        modSel.innerHTML = ['<option value="">All modules</option>'].concat(
            report.moduleBreakdown.slice()
                .sort((a, b) => (a.moduleLabel || '').localeCompare(b.moduleLabel || ''))
                .map(m => `<option value="${IndexLibrary.escapeHtml(m.sourceModule)}">${IndexLibrary.escapeHtml(m.moduleLabel || m.sourceModule)}</option>`)
        ).join('');
        modSel.value = _urModule;
        _urFiltersPopulated = true;
    }

    const userWrap = document.getElementById('ur-user-filter-wrap');
    if (!userWrap) return;
    if (!UR_SHOW_USER_BREAKDOWN) { userWrap.style.display = 'none'; return; }
    userWrap.style.display = '';

    // Only refresh the option list when no single user is selected — otherwise the report is
    // already scoped to that one user and would drop the rest of the choices.
    if (!_urUser) {
        const userSel = document.getElementById('ur-user-filter');
        const users = Array.isArray(report.userBreakdown) ? report.userBreakdown : [];
        userSel.innerHTML = ['<option value="">All users</option>'].concat(
            users.slice()
                .sort((a, b) => (a.userEmail || '').localeCompare(b.userEmail || ''))
                .map(u => `<option value="${IndexLibrary.escapeHtml(u.userEmail)}">${IndexLibrary.escapeHtml(u.userEmail)}</option>`)
        ).join('');
        userSel.value = '';
    }
}

function onUsageReportFilterChange() {
    const modSel = document.getElementById('ur-module-filter');
    const userSel = document.getElementById('ur-user-filter');
    _urModule = modSel ? modSel.value : '';
    _urUser = (UR_SHOW_USER_BREAKDOWN && userSel) ? userSel.value : '';
    const sel = document.getElementById('ur-month-select');
    const month = sel ? sel.value : (_urReport && _urReport.month);
    Promise.all([
        LoadUsageReportTrend(false),   // refresh the chart with filters, keep the month dropdown
        LoadUsageReportMonth(month)
    ]).catch(e => console.error('Usage Report filter change failed:', e));
}

function onUsageReportMonthChange() {
    const sel = document.getElementById('ur-month-select');
    if (sel) LoadUsageReportMonth(sel.value).catch(e => console.error('Usage Report month load failed:', e));
}

// Round a value up to a "nice" axis maximum (1/2/2.5/5/10 × power of ten).
function urNiceCeil(v) {
    if (!(v > 0)) return 1;
    const pow = Math.pow(10, Math.floor(Math.log10(v)));
    const n = v / pow;
    const nice = n <= 1 ? 1 : n <= 2 ? 2 : n <= 2.5 ? 2.5 : n <= 5 ? 5 : 10;
    return nice * pow;
}

// Compact axis label: 12345 -> "12.3K", 2000000 -> "2M".
function urAxisFmt(n) {
    n = Number(n || 0);
    if (n >= 1e6) return (n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1) + 'M';
    if (n >= 1e3) return (n / 1e3).toFixed(n % 1e3 === 0 ? 0 : 1) + 'K';
    return String(Math.round(n));
}

function renderUsageReportTrend(trend) {
    const host = document.getElementById('ur-trend-chart');
    if (!host) return;
    const totals = trend.map(t => (t.interactiveAiTokensUsed || 0) + (t.agentTokensUsed || 0));
    const niceMax = urNiceCeil(Math.max(1, ...totals));

    // Y-axis: 5 evenly spaced ticks from niceMax down to 0. Bars are scaled to niceMax so
    // their heights line up with the ticks.
    const ticks = [1, 0.75, 0.5, 0.25, 0]
        .map(f => `<span class="ur-ytick">${urAxisFmt(niceMax * f)}</span>`).join('');

    const cols = trend.map(t => {
        const aiH = Math.round(((t.interactiveAiTokensUsed || 0) / niceMax) * 100);
        const agH = Math.round(((t.agentTokensUsed || 0) / niceMax) * 100);
        const tip = `${t.monthLabel}: Interactive ${urFmt(t.interactiveAiTokensUsed)}, Agent ${urFmt(t.agentTokensUsed)}`;
        return `<div class="ur-tcol" title="${tip}">
            <div class="ur-tbars">
                <div class="ur-tbar ur-tbar-ai" style="height:${aiH}%"></div>
                <div class="ur-tbar ur-tbar-agent" style="height:${agH}%"></div>
            </div>
            <div class="ur-tlabel">${IndexLibrary.escapeHtml(t.monthLabel || t.month)}</div>
        </div>`;
    }).join('');

    host.innerHTML = `<div class="ur-yaxis">${ticks}</div><div class="ur-plot">${cols}</div>`;
}

function renderUsageReportMonth(report) {
    const monthLabel = urMonthLabel(report.month);
    const modules = Array.isArray(report.moduleBreakdown) ? report.moduleBreakdown : [];
    const isEmpty = (report.totalActivityCount || 0) === 0 && modules.every(m => (m.tokensUsed || 0) === 0);

    document.getElementById('ur-empty-state').style.display = isEmpty ? 'block' : 'none';
    document.getElementById('ur-report-body').style.display = isEmpty ? 'none' : 'block';
    document.getElementById('ur-empty-month').textContent = monthLabel;

    // KPI cards
    const kpis = [
        { label: 'Interactive AI Tokens', value: urFmt(report.interactiveAiTokensUsed), icon: 'bi-lightning-charge-fill', bg: '#eff6fc', fg: '#0078d4' },
        { label: 'Agent Tokens', value: urFmt(report.agentTokensUsed), icon: 'bi-robot', bg: '#f3eefb', fg: '#5c2d91' },
        { label: 'Total Activity', value: urFmt(report.totalActivityCount), icon: 'bi-activity', bg: '#eefbf0', fg: '#107c10' },
        { label: 'Active Users', value: urFmt(report.activeUserCount), icon: 'bi-people-fill', bg: '#fff7e6', fg: '#ca8a00' }
    ];
    document.getElementById('ur-kpi-grid').innerHTML = kpis.map(k => `
        <div class="ur-kpi-card">
            <div class="ur-kpi-icon" style="background:${k.bg};color:${k.fg}"><i class="bi ${k.icon}"></i></div>
            <div class="ur-kpi-label">${k.label}</div>
            <div class="ur-kpi-value">${k.value}</div>
        </div>`).join('');

    if (isEmpty) return;

    // Per-module table
    document.getElementById('ur-module-month').textContent = monthLabel;
    const moduleTotal = modules.reduce((s, m) => s + (m.tokensUsed || 0), 0) || 1;
    const sortedModules = modules.slice().sort((a, b) => (b.tokensUsed || 0) - (a.tokensUsed || 0));
    document.getElementById('ur-module-tbody').innerHTML = sortedModules.map(m => {
        const pct = Math.round(((m.tokensUsed || 0) / moduleTotal) * 100);
        return `<tr>
            <td><span class="ur-module-pill"><span class="ur-module-dot" style="background:${urModuleColor(m.group)}"></span>${IndexLibrary.escapeHtml(m.moduleLabel || m.sourceModule)}</span></td>
            <td>${urFmt(m.activityCount)}</td>
            <td>${urFmt(m.tokensUsed)}</td>
            <td>${pct}%<span class="ur-pct-track"><span class="ur-pct-fill" style="width:${pct}%"></span></span></td>
        </tr>`;
    }).join('') || '<tr><td colspan="4" class="text-muted" style="padding:14px">No module activity this month.</td></tr>';

    // Per-user table — only when the API actually returns it AND the VR-3 gate is satisfied.
    const userCard = document.getElementById('ur-user-card');
    const users = Array.isArray(report.userBreakdown) ? report.userBreakdown : null;
    if (UR_SHOW_USER_BREAKDOWN && users) {
        document.getElementById('ur-user-month').textContent = monthLabel;
        const sortedUsers = users.slice().sort((a, b) => (b.totalTokensUsed || 0) - (a.totalTokensUsed || 0));
        document.getElementById('ur-user-tbody').innerHTML = sortedUsers.map(u => `
            <tr><td>${IndexLibrary.escapeHtml(u.userEmail)}</td><td>${urFmt(u.activityCount)}</td><td>${urFmt(u.totalTokensUsed)}</td></tr>`).join('')
            || '<tr><td colspan="3" class="text-muted" style="padding:14px">No user activity this month.</td></tr>';
        userCard.style.display = '';
    } else {
        userCard.style.display = 'none';
    }
}

function urMonthLabel(monthKey) {
    const t = (_urTrend || []).find(x => x.month === monthKey);
    return t ? (t.monthLabel || t.month) : monthKey;
}

function urDownloadCsv(lines, filenameSuffix) {
    const blob = new Blob([lines.join('\n')], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `usage-report-${filenameSuffix || 'export'}.csv`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

// Common report header (org + active filters) shared by all three export modes.
function urExportHeaderLines(title) {
    const lines = [];
    lines.push('DevOps Assist AI — ' + title);
    lines.push(['Organization', urCsvCell(_urOrgName)].join(','));
    if (_urModule) lines.push(['Module filter', urCsvCell(_urModule)].join(','));
    if (UR_SHOW_USER_BREAKDOWN && _urUser) lines.push(['User filter', urCsvCell(_urUser)].join(','));
    return lines;
}

// mode: 'month' (12-month trend), 'module' (selected month by source module), 'user' (selected month by user)
function exportUsageReport(mode) {
    if (mode === 'month') return exportUsageReportByMonth();
    if (mode === 'user') return exportUsageReportByUser();
    return exportUsageReportByModule();
}

function exportUsageReportByMonth() {
    if (!Array.isArray(_urTrend) || !_urTrend.length) return;
    const lines = urExportHeaderLines('Usage Report — By Month');
    lines.push('');
    lines.push('Monthly Trend (last 12 months)');
    lines.push(['Month', 'Interactive AI tokens', 'Agent tokens', 'Total tokens'].join(','));
    // Chronological ascending, matching the on-screen chart order.
    _urTrend.forEach(t => {
        const total = (t.interactiveAiTokensUsed || 0) + (t.agentTokensUsed || 0);
        lines.push([urCsvCell(t.monthLabel || t.month), t.interactiveAiTokensUsed || 0, t.agentTokensUsed || 0, total].join(','));
    });
    urDownloadCsv(lines, 'by-month');
}

function exportUsageReportByModule() {
    if (!_urReport) return;
    const r = _urReport;
    const monthLabel = urMonthLabel(r.month);
    const lines = urExportHeaderLines('Usage Report — By Module');
    lines.push(['Month', urCsvCell(monthLabel)].join(','));
    lines.push(['Interactive AI tokens', r.interactiveAiTokensUsed || 0].join(','));
    lines.push(['Agent tokens', r.agentTokensUsed || 0].join(','));
    lines.push(['Total activity', r.totalActivityCount || 0].join(','));
    lines.push(['Active users', r.activeUserCount || 0].join(','));
    lines.push('');
    lines.push('Per-Module Breakdown');
    lines.push(['Module', 'Group', 'Activity count', 'Tokens used'].join(','));
    (r.moduleBreakdown || []).forEach(m => {
        lines.push([urCsvCell(m.moduleLabel || m.sourceModule), urCsvCell(m.group), m.activityCount || 0, m.tokensUsed || 0].join(','));
    });
    urDownloadCsv(lines, `by-module-${r.month || 'export'}`);
}

function exportUsageReportByUser() {
    if (!_urReport) return;
    const r = _urReport;
    if (!UR_SHOW_USER_BREAKDOWN || !Array.isArray(r.userBreakdown)) return;
    const monthLabel = urMonthLabel(r.month);
    const lines = urExportHeaderLines('Usage Report — By User');
    lines.push(['Month', urCsvCell(monthLabel)].join(','));
    lines.push(['Active users', r.activeUserCount || 0].join(','));
    lines.push('');
    lines.push('Per-User Breakdown');
    lines.push(['User', 'Activity count', 'Interactive tokens', 'Agent tokens', 'Total tokens'].join(','));
    r.userBreakdown.forEach(u => {
        lines.push([urCsvCell(u.userEmail), u.activityCount || 0, u.interactiveAiTokensUsed || 0, u.agentTokensUsed || 0, u.totalTokensUsed || 0].join(','));
    });
    urDownloadCsv(lines, `by-user-${r.month || 'export'}`);
}

function blockInvalidNumberKeys(e) {
    const invalidKeys = ['e', 'E', '+', '-', '.'];
    if (invalidKeys.includes(e.key)) {
        e.preventDefault();
        return false;
    }
    return true;
}
function openUserGuideLink() {
    IndexLibrary.openInNewTab(userguidelink);
}

function openUserHelpLink() {
    IndexLibrary.openInNewTab(userhelplink);
}

async function toggleTabsBasedOnFeatures() {
    try {
        const features = await IndexLibrary.checkFeatures();
        isDevOpsAssistEnabled = features.devopsAssist === "enable";
        isTimesheetEnabled = features.timesheet === "enable";
        isEvolveEnabled = features.evolve === "enable";
        isReleaseEnabled = features.release === "enable";
        isSummaryEnabled = features.summary === "enable";
        isCodeReviewerEnabled = features.coderevieweragent === "enable";
        isDeveloperAgentEnabled = features.codedeveloperagent === "enable";
        isCleoEnabled = features.cleo === "enable";
        isGroomEnabled = features.groom === "enable";
        if (features.devopsAssist === "enable") {
            document.getElementById("rowDevOpsAssist").style.display = "table-row";
        } else {
            document.getElementById("rowDevOpsAssist").style.display = "none";
        }

        document.getElementById("licenseTableRightCol").style.display = isDevOpsAssistEnabled ? "" : "none";
        document.getElementById("licenseTableLeftCol").classList.toggle("license-table-col--fixed", !isDevOpsAssistEnabled);
        if (features.coderevieweragent === "enable") {
            document.getElementById("rowCodeReviewer").style.display = "table-row";
        } else {
            document.getElementById("rowCodeReviewer").style.display = "none";
        }

        if (features.codedeveloperagent === "enable") {
            document.getElementById("rowDeveloperAgent").style.display = "table-row";
            document.getElementById("rowPrototypeAgent").style.display = "table-row";
        } else {
            document.getElementById("rowDeveloperAgent").style.display = "none";
            document.getElementById("rowPrototypeAgent").style.display = "none";
        }

        if (features.cleo === "enable") {
            document.getElementById("rowCleo").style.display = "table-row";
        } else {
            document.getElementById("rowCleo").style.display = "none";
        }

        if (features.timesheet === "enable") {
            document.getElementById("rowTimesheet").style.display = "table-row";
        } else {
            document.getElementById("rowTimesheet").style.display = "none";
        }

        const tabFeatureMap = {
            "nav-evolveconfigurations-tab": "evolve",
            "nav-summarizeconfigurations-tab": "summary",
            "nav-releaseconfigurations-tab": "release",
            "nav-timesheetconfigurations-tab": "timesheet",
            "nav-codebotai-tab": "coderevieweragent"
        };

        Object.entries(tabFeatureMap).forEach(([tabId, featureKey]) => {
            const tab = document.getElementById(tabId);
            if (tab) {
                tab.style.display = features[featureKey] === "enable" ? "block" : "none";
            }
        });

    } catch (error) {
        console.error("Failed to toggle UI based on features:", error);
    }
}