const { validationResult } = require('express-validator');
const Contact = require('../models/Contact');
const Organization = require('../models/Organization');
const GroupContact = require('../models/GroupContact');
const { buildPublicUploadPath, ensureAbsoluteUrl, ensureImagesRelativePath } = require('../middleware/fileUpload');
const fs = require('fs');
const path = require('path');
const parseInteger = (value) => {
    if (value === undefined || value === null || value === '')
        return null;
    const parsed = Number(value);
    return Number.isNaN(parsed) ? null : parsed;
};
class ContactController {
    // Helper function to convert relative logo path to full URL
    static addLogoUrl(req, contact) {
        try {
            if (!contact) {
                return null;
            }
            const contactJson = contact.toJSON ? contact.toJSON() : contact;
            if (!contactJson) {
                return null;
            }
            if (contactJson.logo) {
                try {
                    const rel = ensureImagesRelativePath('contacts', contactJson.logo);
                    contactJson.logo = ensureAbsoluteUrl(req, rel);
                }
                catch { }
            }
            if (contactJson.avatar) {
                try {
                    const relA = ensureImagesRelativePath('contacts', contactJson.avatar);
                    contactJson.avatar = ensureAbsoluteUrl(req, relA);
                }
                catch { }
            }
            if (!contactJson.unit && contactJson.unit_name)
                contactJson.unit = contactJson.unit_name;
            if (!contactJson.department && contactJson.department_name)
                contactJson.department = contactJson.department_name;
            if (!contactJson.designation && contactJson.designation_name)
                contactJson.designation = contactJson.designation_name;
            return contactJson;
        }
        catch (error) {
            console.error('❌ Error in addLogoUrl:', error);
            try {
                const cj = contact.toJSON ? contact.toJSON() : contact;
                if (cj && cj.logo) {
                    try {
                        cj.logo = ensureAbsoluteUrl(req, cj.logo);
                    }
                    catch { }
                }
                if (cj && cj.avatar) {
                    try {
                        cj.avatar = ensureAbsoluteUrl(req, cj.avatar);
                    }
                    catch { }
                }
                return cj;
            }
            catch {
                return null;
            }
        }
    }
    // Get all contacts with enhanced tree structure based on units
    static async getAllContacts(req, res, next) {
        try {
            const { page = 1, limit = 100, search = '', parent_id = null, unit_id = '', department_id = '', tree = 'true', format = 'tree', // 'tree', 'flat', 'hierarchical'
            include_inactive = 'false', role_filter = 'all', // 'all', 'super_admin', 'admin', 'user'
            source = 'contacts' } = req.query;
            const isSuperAdmin = (user) => {
                if (!user)
                    return false;
                const roleName = (user.role || '').toLowerCase();
                return user.role_id === 1 || roleName === 'super admin' || roleName === 'super_admin';
            };
            const requestedOrgId = parseInteger(req.query.organization_id);
            const organization_id = isSuperAdmin(req.user)
                ? (requestedOrgId || null)
                : (req.user?.organization_id || null);
            const options = {
                page: parseInt(page),
                limit: parseInt(limit),
                search,
                parent_id: parent_id === 'null' ? null : parent_id,
                department_id,
                organization_id,
                include_children: tree === 'true',
                include_inactive: include_inactive === 'true',
                role_filter
            };
            let contacts;
            let formattedContacts;
            if (tree === 'true') {
                // Get tree structure
                contacts = source === 'contacts'
                    ? await Contact.getContactTreeFromContacts(options)
                    : await Contact.getContactTree(options);
                // Format based on requested format
                switch (format) {
                    case 'flat':
                        formattedContacts = ContactController.flattenTreeStructure(contacts);
                        // Apply logo URL to flat structure
                        formattedContacts = formattedContacts.map(contact => ContactController.addLogoUrl(req, contact) || contact);
                        break;
                    case 'hierarchical':
                        formattedContacts = ContactController.formatHierarchicalStructure(contacts, req);
                        break;
                    case 'tree':
                    default:
                        formattedContacts = ContactController.formatTreeStructure(contacts, req);
                        break;
                }
            }
            else {
                // Get flat list
                contacts = source === 'contacts'
                    ? await Contact.findAllFromContacts(options)
                    : await Contact.findAll(options);
                formattedContacts = contacts.map(contact => ContactController.addLogoUrl(req, contact) || contact.toJSON());
            }
            // Get statistics
            const statistics = source === 'contacts'
                ? await Contact.getContactStatisticsFromContacts({ organization_id })
                : await Contact.getEnhancedStatistics(options);
            res.status(200).json({
                status: 'success',
                message: 'Contacts retrieved successfully',
                data: {
                    contacts: formattedContacts,
                    statistics,
                    pagination: {
                        page: parseInt(page),
                        limit: parseInt(limit),
                        total: contacts.length,
                        format
                    },
                    filters: {
                        search,
                        unit_id,
                        department_id,
                        tree: tree === 'true',
                        include_inactive: include_inactive === 'true',
                        role_filter,
                        source
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get contact by ID
    static async getContactById(req, res, next) {
        try {
            const { id } = req.params;
            const isSuperAdmin = (user) => {
                if (!user)
                    return false;
                const roleName = (user.role || '').toLowerCase();
                return user.role_id === 1 || roleName === 'super admin' || roleName === 'super_admin';
            };
            const requestedOrgId = parseInteger(req.query.organization_id);
            const organization_id = isSuperAdmin(req.user)
                ? (requestedOrgId || null)
                : (req.user?.organization_id || null);
            // Prefer contacts table for full details
            let contact = await Contact.findByIdFromContacts(id, organization_id);
            if (!contact) {
                contact = await Contact.findById(id, organization_id);
            }
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            // Get children if requested
            if (req.query.include_children === 'true') {
                const children = await Contact.getChildren(id);
                contact.children = children;
            }
            const normalized = ContactController.addLogoUrl(req, contact) || contact.toJSON();
            res.status(200).json({
                status: 'success',
                message: 'Contact retrieved successfully',
                data: {
                    contact: normalized
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Create new contact with enhanced tree structure support
    static async createContact(req, res, next) {
        try {
            // Check for validation errors
            const errors = validationResult(req);
            if (!errors.isEmpty()) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Validation failed',
                    errors: errors.array()
                });
            }
            const { name, rank_id, service_no, unit_id, department_id, designation_id, phone, mobile, alternative_mobile, present_address, present_lat_lng, office_address, office_lat_lng, permanent_address, permanent_lat_lng, occupation, organization, designation, facebook, linkedin, email, parent_id, order_id, organization_id: bodyOrganizationId, role_id = 5, status = 'offline', avatar = null, logo = null } = req.body;
            const normalizedBodyOrganizationId = parseInteger(bodyOrganizationId);
            const userIsSuperAdmin = req.user?.role === 'Super Admin' || req.user?.role_id === 1;
            let organizationId = req.user?.organization_id || null;
            if (userIsSuperAdmin && normalizedBodyOrganizationId) {
                organizationId = normalizedBodyOrganizationId;
            }
            if (!organizationId) {
                organizationId = normalizedBodyOrganizationId || 1;
            }
            if (!userIsSuperAdmin) {
                const org = await Organization.findById(organizationId);
                const rfp = org ? org.toJSON().required_field_permissions : null;
                const required = Array.isArray(rfp) ? rfp.map(v => String(v).trim()).filter(Boolean) : [];
                if (required.length > 0) {
                    const missing = [];
                    const hasVal = (v) => !(v === undefined || v === null || (typeof v === 'string' && v.trim() === ''));
                    const normalizedUnitId = parseInteger(unit_id);
                    const normalizedDepartmentId = parseInteger(department_id);
                    const normalizedDesignationId = parseInteger(designation_id);
                    const finalEmailTmp = (email === undefined || email === null) ? null : String(email).trim();
                    const loginMobileTmp = (() => {
                        if (mobile === undefined || mobile === null)
                            return '';
                        let normalized = String(mobile).trim().replace(/\s+/g, '');
                        if (normalized.startsWith('+88'))
                            normalized = normalized.substring(3);
                        else if (normalized.startsWith('88'))
                            normalized = normalized.substring(2);
                        return normalized;
                    })();
                    const formattedMobileTmp = (() => {
                        if (mobile === undefined || mobile === null)
                            return '';
                        let normalized = String(mobile).trim().replace(/\s+/g, '');
                        if (normalized === '')
                            return '';
                        if (normalized.startsWith('+88'))
                            return normalized;
                        if (normalized.startsWith('88'))
                            normalized = normalized.substring(2);
                        if (!normalized.startsWith('+88'))
                            normalized = `+88${normalized}`;
                        return normalized;
                    })();
                    for (const f of required) {
                        switch (f) {
                            case 'name':
                                if (!hasVal(name))
                                    missing.push('name');
                                break;
                            case 'rank':
                                if (!hasVal(rank_id))
                                    missing.push('rank');
                                break;
                            case 'service':
                                if (!hasVal(service_no))
                                    missing.push('service');
                                break;
                            case 'login':
                                if (!hasVal(loginMobileTmp))
                                    missing.push('login');
                                break;
                            case 'unit':
                                if (!hasVal(normalizedUnitId))
                                    missing.push('unit');
                                break;
                            case 'department':
                                if (!hasVal(normalizedDepartmentId))
                                    missing.push('department');
                                break;
                            case 'designation':
                                if (!hasVal(normalizedDesignationId))
                                    missing.push('designation');
                                break;
                            case 'designation_name':
                                if (!hasVal(designation))
                                    missing.push('designation_name');
                                break;
                            case 'organization': {
                                // Treat organization as satisfied if organization_id is present
                                const hasOrgName = hasVal(organization);
                                const hasOrgId = !!organizationId;
                                if (!hasOrgName && !hasOrgId)
                                    missing.push('organization');
                                break;
                            }
                            case 'phone':
                                if (!hasVal(phone))
                                    missing.push('phone');
                                break;
                            case 'mobile': {
                                // Allow either mobile or phone to satisfy policy
                                const hasMobile = hasVal(formattedMobileTmp);
                                const hasPhone = hasVal(phone);
                                if (!hasMobile && !hasPhone)
                                    missing.push('mobile');
                                break;
                            }
                            case 'alternative_mobile':
                                if (!hasVal(alternative_mobile))
                                    missing.push('alternative_mobile');
                                break;
                            case 'present_address':
                                if (!hasVal(present_address))
                                    missing.push('present_address');
                                break;
                            case 'present_lat_lng':
                                if (!hasVal(present_lat_lng))
                                    missing.push('present_lat_lng');
                                break;
                            case 'office_address':
                                if (!hasVal(office_address))
                                    missing.push('office_address');
                                break;
                            case 'office_lat_lng':
                                if (!hasVal(office_lat_lng))
                                    missing.push('office_lat_lng');
                                break;
                            case 'permanent_address':
                                if (!hasVal(permanent_address))
                                    missing.push('permanent_address');
                                break;
                            case 'permanent_lat_lng':
                                if (!hasVal(permanent_lat_lng))
                                    missing.push('permanent_lat_lng');
                                break;
                            case 'email':
                                if (!hasVal(finalEmailTmp))
                                    missing.push('email');
                                break;
                            case 'occupation':
                                if (!hasVal(occupation))
                                    missing.push('occupation');
                                break;
                            case 'facebook':
                                if (!hasVal(facebook))
                                    missing.push('facebook');
                                break;
                            case 'linkedin':
                                if (!hasVal(linkedin))
                                    missing.push('linkedin');
                                break;
                            case 'logo':
                                if (!hasVal(logo) && !req.file)
                                    missing.push('logo');
                                break;
                            default: break;
                        }
                    }
                    if (missing.length > 0) {
                        return res.status(400).json({
                            status: 'error',
                            message: 'Required fields missing',
                            errors: missing.map(m => ({ field: m, message: 'This field is required by organization policy' }))
                        });
                    }
                }
            }
            const normalizeOptionalString = (value) => {
                if (value === undefined || value === null)
                    return null;
                const trimmed = String(value).trim();
                return trimmed === '' ? null : trimmed;
            };
            const formatMobileWithCountryCode = (value) => {
                if (value === undefined || value === null)
                    return '';
                let normalized = String(value).trim().replace(/\s+/g, '');
                if (normalized === '')
                    return '';
                if (normalized.startsWith('+88')) {
                    return normalized;
                }
                if (normalized.startsWith('88')) {
                    normalized = normalized.substring(2);
                }
                if (!normalized.startsWith('+88')) {
                    normalized = `+88${normalized}`;
                }
                return normalized;
            };
            const deriveLoginIdFromMobile = (value) => {
                if (value === undefined || value === null)
                    return '';
                let normalized = String(value).trim().replace(/\s+/g, '');
                if (normalized.startsWith('+88')) {
                    normalized = normalized.substring(3);
                }
                else if (normalized.startsWith('88')) {
                    normalized = normalized.substring(2);
                }
                return normalized;
            };
            const rawMobile = mobile !== undefined && mobile !== null ? String(mobile).trim() : '';
            const formattedMobile = formatMobileWithCountryCode(mobile);
            const loginMobile = deriveLoginIdFromMobile(mobile);
            let logoPath = null;
            const normalizedUnitId = parseInteger(unit_id);
            const normalizedDepartmentId = parseInteger(department_id);
            const normalizedDesignationId = parseInteger(designation_id);
            const normalizedParentId = parseInteger(parent_id);
            const finalServiceNo = normalizeOptionalString(service_no);
            const finalEmail = normalizeOptionalString(email);
            const finalOccupation = normalizeOptionalString(occupation);
            const finalOrganization = normalizeOptionalString(organization);
            const finalDesignation = normalizeOptionalString(designation);
            const finalFacebook = normalizeOptionalString(facebook);
            const finalLinkedin = normalizeOptionalString(linkedin);
            const finalServiceOrGenerated = finalServiceNo || `SVC${Date.now()}`;
            const parsedOrderId = parseInteger(order_id);
            const finalOrderId = parsedOrderId === null ? 1 : parsedOrderId;
            // Use normalized mobile for login_id (without country code)
            const finalLoginId = loginMobile || null;
            // If this mobile/login already exists, update that contact instead of creating new
            const existingContact = finalLoginId ? await Contact.findByLoginId(finalLoginId) : null;
            if (existingContact) {
                if (existingContact.organization_id !== organizationId) {
                    return res.status(400).json({
                        status: 'error',
                        message: 'Mobile number already exists for another organization'
                    });
                }
                const updatedUnitId = normalizedUnitId ?? existingContact.unit_id;
                const updatedDepartmentId = normalizedDepartmentId ?? existingContact.department_id;
                const updatedDesignationId = normalizedDesignationId ?? existingContact.designation_id;
                const updatedParentId = normalizedParentId ?? existingContact.parent_id;
                const updatedServiceNo = finalServiceNo !== null ? finalServiceNo : existingContact.service_no;
                const orgValidation = await ContactController.validateOrganizationalStructure(updatedUnitId, updatedDepartmentId, updatedParentId);
                if (!orgValidation.valid) {
                    return res.status(400).json({
                        status: 'error',
                        message: orgValidation.message
                    });
                }
                const updatePayload = {
                    name: name ?? existingContact.name,
                    rank_id: rank_id ?? existingContact.rank_id,
                    service_no: updatedServiceNo,
                    unit_id: updatedUnitId,
                    department_id: updatedDepartmentId,
                    designation_id: updatedDesignationId,
                    phone: normalizeOptionalString(phone),
                    mobile: formattedMobile,
                    alternative_mobile: alternative_mobile ? formatMobileWithCountryCode(alternative_mobile) : null,
                    present_address: normalizeOptionalString(present_address),
                    present_lat_lng: normalizeOptionalString(present_lat_lng),
                    office_address: normalizeOptionalString(office_address),
                    office_lat_lng: normalizeOptionalString(office_lat_lng),
                    permanent_address: normalizeOptionalString(permanent_address),
                    permanent_lat_lng: normalizeOptionalString(permanent_lat_lng),
                    occupation: finalOccupation,
                    organization: finalOrganization,
                    designation: finalDesignation,
                    facebook: finalFacebook,
                    linkedin: finalLinkedin,
                    email: finalEmail ?? existingContact.email,
                    parent_id: updatedParentId
                };
                if (req.file) {
                    logoPath = buildPublicUploadPath('contacts', req.file.filename);
                    console.log('📸 Updating contact - Logo relative path to save in DB:', logoPath);
                }
                else if (logo) {
                    logoPath = logo;
                }
                if (logoPath !== null) {
                    updatePayload.logo = logoPath;
                }
                await existingContact.update(updatePayload);
                // Refresh from DB to use authoritative values
                const refreshedExisting = await Contact.findById(existingContact.id);
                await ContactController.syncGroupContactMapping(refreshedExisting.id, refreshedExisting.department_id || updatedDepartmentId || null, refreshedExisting.organization_id || organizationId);
                const updatedContact = await Contact.findById(existingContact.id);
                const contactWithLogo = ContactController.addLogoUrl(req, updatedContact);
                return res.status(200).json({
                    status: 'success',
                    message: 'Existing contact updated successfully.',
                    data: {
                        contact: contactWithLogo
                    }
                });
            }
            // Use mobile number as password source
            const bcrypt = require('bcryptjs');
            const finalPasswordHash = loginMobile ? await bcrypt.hash(loginMobile, 12) : await bcrypt.hash('000000', 12);
            // Validate parent-child relationship
            if (normalizedParentId) {
                const parentValidation = await ContactController.validateParentChildRelationship(normalizedParentId, normalizedUnitId);
                if (!parentValidation.valid) {
                    return res.status(400).json({
                        status: 'error',
                        message: parentValidation.message
                    });
                }
            }
            // Handle logo upload if present
            if (req.file) {
                // Save only relative path in database (e.g., /Images/user/filename.jpg)
                logoPath = buildPublicUploadPath('contacts', req.file.filename);
                console.log('📸 Creating contact - Logo relative path to save in DB:', logoPath);
            }
            else if (logo) {
                // If logo is provided as string (from req.body), use it directly
                logoPath = logo;
            }
            // Do not derive unit_id from department; departments no longer carry unit linkage
            let derivedUnitId = normalizedUnitId;
            // Avoid unique constraint violation on contacts.email: if duplicate exists, null out email
            try {
                if (finalEmail) {
                    const { pool } = require('../config/database');
                    const [dupRows] = await pool.execute('SELECT id FROM contacts WHERE email = ? LIMIT 1', [finalEmail]);
                    if (dupRows && dupRows.length > 0) {
                        finalEmail = null;
                    }
                }
            }
            catch { }
            // Create contact with enhanced data following database structure
            const contactData = {
                name: name || null,
                organization_id: organizationId,
                rank_id: rank_id || null,
                service_no: finalServiceOrGenerated,
                login_id: finalLoginId,
                unit_id: derivedUnitId,
                department_id: normalizedDepartmentId,
                designation_id: normalizedDesignationId,
                order_id: finalOrderId,
                phone: normalizeOptionalString(phone),
                mobile: formattedMobile || null,
                alternative_mobile: alternative_mobile ? formatMobileWithCountryCode(alternative_mobile) : null,
                present_address: normalizeOptionalString(present_address),
                present_lat_lng: normalizeOptionalString(present_lat_lng),
                office_address: normalizeOptionalString(office_address),
                office_lat_lng: normalizeOptionalString(office_lat_lng),
                permanent_address: normalizeOptionalString(permanent_address),
                permanent_lat_lng: normalizeOptionalString(permanent_lat_lng),
                occupation: finalOccupation,
                organization: finalOrganization,
                designation: finalDesignation,
                facebook: finalFacebook,
                linkedin: finalLinkedin,
                email: finalEmail,
                parent_id: normalizedParentId,
                password_hash: finalPasswordHash,
                role_id,
                status,
                avatar,
                logo: logoPath
            };
            // Set default role_id if not provided (following database structure)
            if (!contactData.role_id) {
                contactData.role_id = 5; // Default to regular user (User role)
            }
            // Validate organizational structure
            const orgValidation = await ContactController.validateOrganizationalStructure(contactData.unit_id, contactData.department_id, contactData.parent_id);
            if (!orgValidation.valid) {
                return res.status(400).json({
                    status: 'error',
                    message: orgValidation.message
                });
            }
            const contactId = await Contact.createInContacts(contactData);
            // Fetch the just-created contact to read org/department from DB
            const createdContact = await Contact.findById(contactId);
            let userCreatedId = null;
            // Auto create login user in users table if organization enables it
            try {
                const orgForAuto = await Organization.findById(organizationId);
                const autoCreateEnabled = !!orgForAuto && (orgForAuto.auto_user_create === 1 || orgForAuto.auto_user_create === true || orgForAuto.auto_user_create === '1');
                if (autoCreateEnabled && finalLoginId) {
                    userCreatedId = await Contact.create({
                        name: contactData.name,
                        organization_id: organizationId,
                        rank_id: rank_id || null,
                        service_no: finalServiceOrGenerated,
                        login_id: finalLoginId,
                        unit_id: contactData.unit_id,
                        department_id: contactData.department_id,
                        designation_id: contactData.designation_id,
                        phone: contactData.phone,
                        mobile: contactData.mobile,
                        alternative_mobile: contactData.alternative_mobile,
                        present_address: contactData.present_address,
                        present_lat_lng: contactData.present_lat_lng,
                        office_address: contactData.office_address,
                        office_lat_lng: contactData.office_lat_lng,
                        permanent_address: contactData.permanent_address,
                        permanent_lat_lng: contactData.permanent_lat_lng,
                        occupation: contactData.occupation,
                        organization: contactData.organization,
                        designation: contactData.designation,
                        facebook: contactData.facebook,
                        linkedin: contactData.linkedin,
                        email: contactData.email,
                        parent_id: contactData.parent_id,
                        password_hash: finalPasswordHash,
                        role_id: 5,
                        status: status,
                        avatar: contactData.avatar,
                        logo: contactData.logo
                    });
                }
            }
            catch (autoErr) {
                console.error('⚠️ Auto user creation failed:', autoErr);
            }
            // Ensure group_contact_mappings uses contacts.id
            const mappingContactId = contactId;
            const mappingDeptId = createdContact?.department_id || contactData.department_id || null;
            const mappingOrgId = createdContact?.organization_id || organizationId;
            await ContactController.syncGroupContactMapping(mappingContactId, mappingDeptId, mappingOrgId);
            try {
                await GroupContact.ensureContactForeignKey();
                await GroupContact.forceInsert(mappingOrgId, mappingDeptId, mappingContactId);
            }
            catch (e) {
                console.error('⚠️ forceInsert fallback failed after syncGroupContactMapping:', e?.message || e);
            }
            // Fetch created contact with full details
            const newContact = await Contact.findById(contactId);
            // Get updated tree structure for the unit
            const treeStructure = [];
            // Get updated statistics
            const statistics = { total_contacts: 0 };
            const newContactWithLogo = ContactController.addLogoUrl(req, newContact) || newContact.toJSON();
            res.status(201).json({
                status: 'success',
                message: 'Contact created successfully',
                data: {
                    contact: newContactWithLogo,
                    tree_structure: ContactController.formatTreeStructure(treeStructure, req),
                    statistics,
                    parent_info: contactData.parent_id ? await Contact.findById(contactData.parent_id) : null
                }
            });
        }
        catch (error) {
            console.error('Error in createContact:', error);
            next(error);
        }
    }
    // Update contact with enhanced tree structure support
    static async updateContact(req, res, next) {
        try {
            const { id } = req.params;
            const isSuperAdmin = (user) => {
                if (!user)
                    return false;
                const roleName = (user.role || '').toLowerCase();
                return user.role_id === 1 || roleName === 'super admin' || roleName === 'super_admin';
            };
            const requestedOrgId = parseInteger(req.query.organization_id);
            const organization_id = isSuperAdmin(req.user)
                ? (requestedOrgId || null)
                : (req.user?.organization_id || null);
            let contact = await Contact.findByIdFromContacts(id, organization_id);
            if (!contact) {
                // Fallback: try users table, but do not update users; return not found in contacts
                const fallback = await Contact.findById(id, organization_id);
                if (fallback && fallback._table === 'users') {
                    return res.status(404).json({
                        status: 'error',
                        message: 'Contact not found in contacts table'
                    });
                }
                contact = fallback;
            }
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            // Check for validation errors
            const errors = validationResult(req);
            if (!errors.isEmpty()) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Validation failed',
                    errors: errors.array()
                });
            }
            const updateData = { ...req.body };
            if (Object.prototype.hasOwnProperty.call(updateData, 'password_hash')) {
                delete updateData.password_hash;
            }
            if (updateData.department_id === '')
                updateData.department_id = null;
            if (updateData.designation_id === '')
                updateData.designation_id = null;
            const nullableStringFields = [
                'phone',
                'mobile',
                'alternative_mobile',
                'organization',
                'designation',
                'present_address',
                'present_lat_lng',
                'office_address',
                'office_lat_lng',
                'permanent_address',
                'permanent_lat_lng'
            ];
            nullableStringFields.forEach(field => {
                if (Object.prototype.hasOwnProperty.call(updateData, field) && updateData[field] === '') {
                    updateData[field] = null;
                }
            });
            const formatMobileWithCountryCode = (value) => {
                if (value === undefined || value === null)
                    return '';
                let normalized = String(value).trim().replace(/\s+/g, '');
                if (normalized === '')
                    return '';
                if (normalized.startsWith('+88')) {
                    return normalized;
                }
                if (normalized.startsWith('88')) {
                    normalized = normalized.substring(2);
                }
                return `+88${normalized}`;
            };
            const deriveLoginIdFromMobile = (value) => {
                if (!value)
                    return '';
                let normalized = String(value).trim().replace(/\s+/g, '');
                if (normalized.startsWith('+88')) {
                    normalized = normalized.substring(3);
                }
                else if (normalized.startsWith('88')) {
                    normalized = normalized.substring(2);
                }
                return normalized;
            };
            if (Object.prototype.hasOwnProperty.call(updateData, 'mobile')) {
                // Mobile is optional if phone number exists
                // Only validate and update if mobile is provided and not empty
                if (updateData.mobile && updateData.mobile.trim() !== '') {
                    const formattedMobile = formatMobileWithCountryCode(updateData.mobile);
                    if (!formattedMobile) {
                        return res.status(400).json({
                            status: 'error',
                            message: 'Invalid mobile number format'
                        });
                    }
                    const derivedLoginId = deriveLoginIdFromMobile(formattedMobile);
                    if (!derivedLoginId) {
                        return res.status(400).json({
                            status: 'error',
                            message: 'Could not derive login ID from mobile number'
                        });
                    }
                    // Update mobile number
                    updateData.mobile = formattedMobile;
                    // Only update login_id if it's different from current
                    if (derivedLoginId !== contact.login_id) {
                        // First, clear any duplicate login_id BEFORE updating current contact
                        // This prevents database constraint violation
                        try {
                            const { pool } = require('../config/database');
                            const organizationId = contact.organization_id || req.user?.organization_id || 1;
                            // Find ALL existing users with same login_id (same and different organization)
                            const [allExistingUsers] = await pool.execute('SELECT id, name, organization_id FROM users WHERE login_id = ? AND is_active = 1 AND id != ?', [derivedLoginId, id]);
                            if (allExistingUsers.length > 0) {
                                // Clear login_id from ALL existing users BEFORE updating current contact
                                for (const existingUser of allExistingUsers) {
                                    await pool.execute('UPDATE users SET login_id = NULL WHERE id = ?', [existingUser.id]);
                                    console.log(`✅ Cleared login_id from user ID ${existingUser.id} (${existingUser.name}) in organization ${existingUser.organization_id} to resolve duplicate`);
                                }
                                console.log(`✅ Cleared ${allExistingUsers.length} duplicate login_id(s) before updating contact ID ${id}`);
                            }
                            // Now safe to update current contact's login_id
                            updateData.login_id = derivedLoginId;
                            console.log(`✅ Will update login_id to '${derivedLoginId}' for contact ID ${id} (organization ${organizationId})`);
                        }
                        catch (error) {
                            console.error('Error clearing duplicate login_id:', error);
                            // If clearing fails, don't update login_id to avoid constraint violation
                            console.log(`⚠️ Could not clear duplicate login_id. Will not update login_id for contact ID ${id}`);
                            // Remove login_id from updateData to prevent constraint violation
                            delete updateData.login_id;
                        }
                    }
                    else {
                        // Same login_id, no need to update
                        // updateData.login_id is already set to current value implicitly
                    }
                }
                else {
                    // Mobile is empty/null - set to null (don't require it if phone exists)
                    updateData.mobile = null;
                    // Don't update login_id if mobile is removed
                }
            }
            if (Object.prototype.hasOwnProperty.call(updateData, 'alternative_mobile')) {
                const formattedAltMobile = formatMobileWithCountryCode(updateData.alternative_mobile);
                updateData.alternative_mobile = formattedAltMobile || null;
            }
            if (Object.prototype.hasOwnProperty.call(updateData, 'organization_id')) {
                updateData.organization_id = parseInteger(updateData.organization_id) || contact.organization_id;
            }
            if (Object.prototype.hasOwnProperty.call(updateData, 'designation_id')) {
                updateData.designation_id = parseInteger(updateData.designation_id) || contact.designation_id;
            }
            if (Object.prototype.hasOwnProperty.call(updateData, 'order_id')) {
                const parsedOrder = parseInteger(updateData.order_id);
                updateData.order_id = parsedOrder !== null ? parsedOrder : 1;
            }
            if (Object.prototype.hasOwnProperty.call(updateData, 'organization_id')) {
                updateData.organization_id = parseInteger(updateData.organization_id) || contact.organization_id;
            }
            if (Object.prototype.hasOwnProperty.call(updateData, 'designation_id')) {
                updateData.designation_id = parseInteger(updateData.designation_id) || contact.designation_id;
            }
            const originalParentId = contact.parent_id;
            // Do not derive unit_id from department; keep provided unit_id or existing contact.unit_id
            // Validate parent-child relationship if parent_id is being updated
            if (updateData.parent_id !== undefined) {
                if (updateData.parent_id) {
                    // Check for self-reference
                    if (updateData.parent_id == id) {
                        return res.status(400).json({
                            status: 'error',
                            message: 'Contact cannot be its own parent'
                        });
                    }
                    // Check for circular reference
                    const circularCheck = await ContactController.checkCircularReference(id, updateData.parent_id);
                    if (circularCheck.hasCircularReference) {
                        return res.status(400).json({
                            status: 'error',
                            message: circularCheck.message
                        });
                    }
                    // Validate parent-child relationship
                    const parentValidation = await ContactController.validateParentChildRelationship(updateData.parent_id, updateData.unit_id || contact.unit_id);
                    if (!parentValidation.valid) {
                        return res.status(400).json({
                            status: 'error',
                            message: parentValidation.message
                        });
                    }
                }
            }
            // Optional duplicate check: proceed even if duplicates exist for service_no/email
            // login_id duplicates are handled below by clearing conflicting login_id values
            const loginIdForDuplicateCheck = Object.prototype.hasOwnProperty.call(updateData, 'login_id') ? updateData.login_id : null;
            try {
                if (loginIdForDuplicateCheck) {
                    const duplicateCheck = await ContactController.checkForDuplicatesOnUpdate(id, null, null, loginIdForDuplicateCheck || contact.login_id);
                    if (duplicateCheck.exists) {
                        console.log('⚠️ Duplicate login_id detected; will resolve before update');
                    }
                }
            }
            catch (e) {
                console.log('⚠️ Duplicate check skipped due to error; proceeding with update');
            }
            // Handle logo upload if present
            if (req.file) {
                // Delete old logo if exists
                if (contact.logo) {
                    try {
                        // Handle both /uploads/user and /Images/user paths
                        const oldLogoPath = contact.logo.replace(/^.*\/(uploads|Images)\//, '');
                        let fullOldPath;
                        if (contact.logo.includes('/Images/contacts/')) {
                            fullOldPath = path.join(__dirname, '..', '..', 'public', 'Images', 'contacts', path.basename(oldLogoPath));
                        }
                        else if (contact.logo.includes('/Images/user/')) {
                            fullOldPath = path.join(__dirname, '..', '..', 'public', 'Images', 'user', path.basename(oldLogoPath));
                        }
                        else {
                            fullOldPath = path.join(__dirname, '..', '..', 'uploads', 'contacts', path.basename(oldLogoPath));
                        }
                        if (fs.existsSync(fullOldPath)) {
                            fs.unlinkSync(fullOldPath);
                            console.log('🗑️ Deleted old logo:', fullOldPath);
                        }
                    }
                    catch (err) {
                        console.error('Error deleting old logo:', err);
                    }
                }
                // Save only relative path in database
                updateData.logo = buildPublicUploadPath('contacts', req.file.filename);
                console.log('📸 Updating contact - Logo relative path to save in DB:', updateData.logo);
            }
            else if (req.body.remove_logo === 'true' || req.body.remove_logo === true) {
                // Handle logo removal
                if (contact.logo) {
                    try {
                        const oldLogoPath = contact.logo.replace(/^.*\/(uploads|Images)\//, '');
                        let fullOldPath;
                        if (contact.logo.includes('/Images/contacts/')) {
                            fullOldPath = path.join(__dirname, '..', '..', 'public', 'Images', 'contacts', path.basename(oldLogoPath));
                        }
                        else if (contact.logo.includes('/Images/user/')) {
                            fullOldPath = path.join(__dirname, '..', '..', 'public', 'Images', 'user', path.basename(oldLogoPath));
                        }
                        else {
                            fullOldPath = path.join(__dirname, '..', '..', 'uploads', 'contacts', path.basename(oldLogoPath));
                        }
                        if (fs.existsSync(fullOldPath)) {
                            fs.unlinkSync(fullOldPath);
                        }
                    }
                    catch (err) {
                        console.error('Error deleting old logo:', err);
                    }
                }
                updateData.logo = null;
            }
            // IMPORTANT: Clear duplicate login_id BEFORE updating to prevent constraint violation
            // This must happen BEFORE the update() call
            if (updateData.login_id && updateData.login_id !== contact.login_id) {
                try {
                    const { pool } = require('../config/database');
                    const [duplicateUsers] = await pool.execute('SELECT id FROM users WHERE login_id = ? AND is_active = 1 AND id != ?', [updateData.login_id, id]);
                    if (duplicateUsers.length > 0) {
                        console.log(`🔍 Found ${duplicateUsers.length} duplicate login_id(s) for '${updateData.login_id}'. Clearing them...`);
                        for (const dupUser of duplicateUsers) {
                            await pool.execute('UPDATE users SET login_id = NULL WHERE id = ?', [dupUser.id]);
                            console.log(`✅ Cleared login_id from user ID ${dupUser.id}`);
                        }
                    }
                }
                catch (error) {
                    console.error('❌ Error clearing duplicate login_id before update:', error);
                    // Remove login_id from updateData to prevent constraint violation
                    delete updateData.login_id;
                    console.log('⚠️ Removed login_id from updateData to prevent constraint violation');
                }
            }
            // Update contact (always in contacts table due to findByIdFromContacts)
            const updated = await contact.update(updateData);
            if (!updated) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Failed to update contact'
                });
            }
            // Fetch updated contact with full details
            const updatedContact = await Contact.findById(id);
            const updatedContactWithLogo = ContactController.addLogoUrl(req, updatedContact) || updatedContact.toJSON();
            await ContactController.syncGroupContactMapping(updatedContact.id, updatedContact.department_id || null, updatedContact.organization_id || req.user?.organization_id || null);
            try {
                await GroupContact.ensureContactForeignKey();
                await GroupContact.forceInsert(updatedContact.organization_id || req.user?.organization_id || null, updatedContact.department_id || null, updatedContact.id);
            }
            catch (e) {
                console.error('⚠️ forceInsert fallback failed after update sync:', e?.message || e);
            }
            // Get updated tree structure for the unit
            const treeStructure = await Contact.getContactTree({
                unit_id: updatedContact.unit_id,
                format: 'tree'
            });
            // Get updated statistics
            const statistics = await Contact.getEnhancedStatistics({
                unit_id: updatedContact.unit_id
            });
            // Check if parent changed and get affected tree structures
            const parentChanged = originalParentId !== updatedContact.parent_id;
            const affectedTrees = {};
            if (parentChanged) {
                // Get tree structure for original parent's unit if different
                if (originalParentId) {
                    const originalParent = await Contact.findById(originalParentId);
                    if (originalParent && originalParent.unit_id !== updatedContact.unit_id) {
                        affectedTrees.original_unit_tree = await Contact.getContactTree({
                            unit_id: originalParent.unit_id,
                            format: 'tree'
                        });
                    }
                }
                // Get tree structure for new parent's unit if different
                if (updatedContact.parent_id) {
                    const newParent = await Contact.findById(updatedContact.parent_id);
                    if (newParent && newParent.unit_id !== updatedContact.unit_id) {
                        affectedTrees.new_unit_tree = await Contact.getContactTree({
                            unit_id: newParent.unit_id,
                            format: 'tree'
                        });
                    }
                }
            }
            res.status(200).json({
                status: 'success',
                message: 'Contact updated successfully',
                data: {
                    contact: updatedContactWithLogo,
                    tree_structure: ContactController.formatTreeStructure(treeStructure, req),
                    statistics,
                    parent_info: updatedContact.parent_id ? await Contact.findById(updatedContact.parent_id) : null,
                    parent_changed: parentChanged,
                    affected_trees: Object.keys(affectedTrees).length > 0 ? affectedTrees : null
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Delete contact
    static async deleteContact(req, res, next) {
        try {
            const { id } = req.params;
            const contactId = parseInt(id);
            const isSuperAdmin = (user) => {
                if (!user)
                    return false;
                const roleName = (user.role || '').toLowerCase();
                return user.role_id === 1 || roleName === 'super admin' || roleName === 'super_admin';
            };
            const requestedOrgId = parseInteger(req.query.organization_id);
            const organization_id = isSuperAdmin(req.user)
                ? (requestedOrgId || null)
                : (req.user?.organization_id || null);
            let contact = await Contact.findByIdFromContacts(contactId, organization_id);
            if (!contact) {
                const fallback = await Contact.findById(contactId, organization_id);
                if (fallback && fallback._table === 'users') {
                    return res.status(404).json({
                        status: 'error',
                        message: 'Contact not found in contacts table'
                    });
                }
                contact = fallback;
            }
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            const departmentParam = req.query.department_id || req.body?.department_id;
            let departmentId = null;
            if (departmentParam !== undefined && departmentParam !== null && departmentParam !== '') {
                const parsedDepartmentId = parseInt(departmentParam, 10);
                departmentId = Number.isNaN(parsedDepartmentId) ? null : parsedDepartmentId;
            }
            const mappings = await GroupContact.getMappings({ contact_id: contactId });
            const mappingCount = mappings.length;
            const scope = req.query.scope || '';
            const source = req.query.source || '';
            // Management delete behavior: if source=management
            if (source === 'management') {
                // Always remove any group mappings first
                if (mappingCount > 0) {
                    const mappingIds = mappings.map(m => m.id);
                    await GroupContact.removeMappings(mappingIds);
                }
                // If contact record lives in contacts table, perform hard delete
                if (contact._table === 'contacts') {
                    const deleted = await contact.delete();
                    return res.status(200).json({
                        status: deleted ? 'success' : 'error',
                        message: deleted ? 'Contact deleted from database (contacts table)' : 'Failed to delete contact',
                        data: { action: 'management_deleted_hard', removed_mappings: mappingCount }
                    });
                }
                // If contact is from users table: hard delete only when already inactive; otherwise soft delete
                try {
                    const cols = await Contact.getTableColumns('users');
                    const hasIsActive = cols.has('is_active');
                    if (contact._table === 'users') {
                        if (hasIsActive && (contact.is_active === 0 || contact.is_active === false)) {
                            const [dbRes] = await pool.execute('DELETE FROM users WHERE id = ?', [contactId]);
                            return res.status(200).json({
                                status: dbRes.affectedRows > 0 ? 'success' : 'error',
                                message: dbRes.affectedRows > 0
                                    ? 'Inactive contact deleted from users table'
                                    : 'Failed to delete inactive contact from users table',
                                data: { action: 'management_deleted_hard', removed_mappings: mappingCount }
                            });
                        }
                        if (hasIsActive) {
                            await pool.execute('UPDATE users SET is_active = 0 WHERE id = ?', [contactId]);
                            return res.status(200).json({
                                status: 'success',
                                message: 'Contact deactivated (users table) and removed from groups',
                                data: { action: 'management_deactivated_soft', removed_mappings: mappingCount }
                            });
                        }
                    }
                }
                catch (_) { }
                return res.status(200).json({
                    status: 'success',
                    message: 'Contact removed from groups',
                    data: { action: 'management_removed_groups_only', removed_mappings: mappingCount }
                });
            }
            if (departmentId || scope === 'department') {
                let targetDepartmentId = null;
                let mappingIdsToRemove = [];
                if (departmentId) {
                    mappingIdsToRemove = mappings
                        .filter(mapping => mapping.department_id === departmentId)
                        .map(mapping => mapping.id);
                    targetDepartmentId = departmentId;
                }
                else if (contact.department_id) {
                    mappingIdsToRemove = mappings
                        .filter(mapping => mapping.department_id === contact.department_id)
                        .map(mapping => mapping.id);
                    if (mappingIdsToRemove.length > 0) {
                        targetDepartmentId = contact.department_id;
                    }
                }
                if (mappingIdsToRemove.length === 0 && mappings.length > 0) {
                    // Fallback: remove the first mapping if we cannot determine a specific department
                    mappingIdsToRemove = [mappings[0].id];
                    targetDepartmentId = mappings[0].department_id || null;
                }
                if (mappingIdsToRemove.length === 0) {
                    try {
                        const cols = await Contact.getTableColumns(contact._table || 'users');
                        if (cols.has('status')) {
                            await contact.update({ status: 'offline' });
                        }
                    }
                    catch (_) { }
                    return res.status(200).json({
                        status: 'success',
                        message: 'No department mapping found; contact status updated',
                        data: {
                            action: 'department_removed',
                            removed_department_id: targetDepartmentId,
                            removed_mappings: 0,
                            remaining_mappings: mappingCount
                        }
                    });
                }
                const removedMappings = await GroupContact.removeMappings(mappingIdsToRemove);
                try {
                    const cols = await Contact.getTableColumns(contact._table || 'users');
                    if (cols.has('status')) {
                        await contact.update({ status: 'offline' });
                    }
                }
                catch (_) { }
                return res.status(200).json({
                    status: 'success',
                    message: 'Contact removed from department and status updated',
                    data: {
                        action: 'department_removed',
                        removed_department_id: targetDepartmentId,
                        removed_mappings: removedMappings,
                        remaining_mappings: Math.max(mappingCount - removedMappings, 0)
                    }
                });
            }
            // Directory delete: remove all group mappings only, keep contact
            let removedMappings = 0;
            if (mappingCount > 0) {
                const mappingIds = mappings.map(mapping => mapping.id);
                removedMappings = await GroupContact.removeMappings(mappingIds);
            }
            return res.status(200).json({
                status: 'success',
                message: 'Contact removed from directory groups',
                data: {
                    action: 'directory_removed',
                    removed_department_id: departmentId || null,
                    removed_mappings: removedMappings,
                    remaining_mappings: 0
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get contact children
    static async getContactChildren(req, res, next) {
        try {
            const { id } = req.params;
            const contact = await Contact.findById(id);
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            const children = await Contact.getChildren(id);
            res.status(200).json({
                status: 'success',
                message: 'Contact children retrieved successfully',
                data: {
                    parent: contact.toJSON(),
                    children: children.map(child => child.toJSON())
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get full hierarchy path for a contact
    static async getContactHierarchy(req, res, next) {
        try {
            const { id } = req.params;
            const contact = await Contact.findById(id);
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            // Build hierarchy path from root to current contact
            const hierarchyPath = await ContactController.buildHierarchyPath(id);
            res.status(200).json({
                status: 'success',
                message: 'Contact hierarchy retrieved successfully',
                data: {
                    contact: contact.toJSON(),
                    hierarchy_path: hierarchyPath,
                    hierarchy_depth: hierarchyPath.length,
                    organizational_info: {
                        unit_name: contact.unit_name,
                        unit_code: contact.unit_code,
                        department_name: contact.department_name,
                        designation_name: contact.designation_name,
                        rank_name: contact.rank_name
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Search contacts
    static async searchContacts(req, res, next) {
        try {
            const { q: searchTerm, unit_id = '', department_id = '', limit = 20, source = 'contacts', include_inactive = 'false' } = req.query;
            const isSuperAdmin = (user) => {
                if (!user)
                    return false;
                const roleName = (user.role || '').toLowerCase();
                return user.role_id === 1 || roleName === 'super admin' || roleName === 'super_admin';
            };
            const requestedOrgId = parseInteger(req.query.organization_id);
            const organization_id = isSuperAdmin(req.user)
                ? (requestedOrgId || null)
                : (req.user?.organization_id || null);
            if (!searchTerm || searchTerm.trim().length < 2) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Search term must be at least 2 characters long'
                });
            }
            const contacts = (source === 'contacts'
                ? await Contact.searchFromContacts(searchTerm.trim(), {
                    department_id,
                    organization_id,
                    limit: parseInt(limit),
                    include_inactive: include_inactive === 'true'
                })
                : await Contact.search(searchTerm.trim(), {
                    unit_id,
                    department_id,
                    organization_id,
                    limit: parseInt(limit)
                }));
            res.status(200).json({
                status: 'success',
                message: 'Search completed successfully',
                data: {
                    contacts: contacts.map(contact => ContactController.addLogoUrl(req, contact) || contact.toJSON()),
                    searchTerm: searchTerm.trim(),
                    filters: {
                        unit_id,
                        department_id,
                        organization_id
                    },
                    total: contacts.length
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get contact statistics
    static async getContactStatistics(req, res, next) {
        try {
            const statistics = await Contact.getStatistics();
            res.status(200).json({
                status: 'success',
                message: 'Contact statistics retrieved successfully',
                data: {
                    statistics
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Bulk operations
    static async bulkDeleteContacts(req, res, next) {
        try {
            const { contactIds } = req.body;
            if (!Array.isArray(contactIds) || contactIds.length === 0) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Contact IDs array is required'
                });
            }
            const results = [];
            const errors = [];
            for (const id of contactIds) {
                try {
                    const contact = await Contact.findById(id);
                    if (!contact) {
                        errors.push({ id, error: 'Contact not found' });
                        continue;
                    }
                    // Check if contact has children
                    const children = await Contact.getChildren(id);
                    if (children.length > 0) {
                        errors.push({ id, error: 'Contact has children' });
                        continue;
                    }
                    const deleted = await contact.delete();
                    if (deleted) {
                        results.push({ id, status: 'deleted' });
                    }
                    else {
                        errors.push({ id, error: 'Failed to delete' });
                    }
                }
                catch (error) {
                    errors.push({ id, error: error.message });
                }
            }
            res.status(200).json({
                status: 'success',
                message: 'Bulk delete operation completed',
                data: {
                    deleted: results,
                    errors,
                    summary: {
                        total: contactIds.length,
                        deleted: results.length,
                        errors: errors.length
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Move contact to different parent
    static async moveContact(req, res, next) {
        try {
            const { id } = req.params;
            const { parent_id } = req.body;
            const contact = await Contact.findById(id);
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            // Check if new parent exists (if parent_id is provided)
            if (parent_id) {
                const parentContact = await Contact.findById(parent_id);
                if (!parentContact) {
                    return res.status(400).json({
                        status: 'error',
                        message: 'Parent contact not found'
                    });
                }
                // Prevent self-reference
                if (parent_id == id) {
                    return res.status(400).json({
                        status: 'error',
                        message: 'Contact cannot be its own parent'
                    });
                }
            }
            // Update parent
            const updated = await contact.update({ parent_id });
            if (!updated) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Failed to move contact'
                });
            }
            // Fetch updated contact
            const updatedContact = await Contact.findById(id);
            res.status(200).json({
                status: 'success',
                message: 'Contact moved successfully',
                data: {
                    contact: updatedContact.toJSON()
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get contact tree by unit with enhanced filtering
    static async getContactTreeByUnit(req, res, next) {
        try {
            const { unitId } = req.params;
            const { search = '', department_id = '', format = 'tree', include_inactive = 'false', role_filter = 'all' } = req.query;
            const options = {
                search,
                department_id,
                include_inactive: include_inactive === 'true',
                role_filter
            };
            // Get contact tree for specific unit
            const contactTree = await Contact.getContactTree(options);
            // Format based on requested format
            let formattedContacts;
            switch (format) {
                case 'flat':
                    formattedContacts = ContactController.flattenTreeStructure(contactTree);
                    // Apply logo URL to flat structure
                    formattedContacts = formattedContacts.map(contact => ContactController.addLogoUrl(req, contact) || contact);
                    break;
                case 'hierarchical':
                    formattedContacts = ContactController.formatHierarchicalStructure(contactTree, req);
                    break;
                case 'tree':
                default:
                    formattedContacts = ContactController.formatTreeStructure(contactTree, req);
                    break;
            }
            // Get unit-specific statistics
            const statistics = await Contact.getEnhancedStatistics(options);
            res.status(200).json({
                status: 'success',
                message: 'Unit contact tree retrieved successfully',
                data: {
                    unit_id: parseInt(unitId),
                    contacts: formattedContacts,
                    statistics,
                    filters: {
                        search,
                        department_id,
                        format,
                        include_inactive: include_inactive === 'true',
                        role_filter
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get contact tree by department
    static async getContactTreeByDepartment(req, res, next) {
        try {
            const { departmentId } = req.params;
            const { search = '', format = 'tree', include_inactive = 'false', role_filter = 'all' } = req.query;
            const options = {
                search,
                department_id: departmentId,
                include_inactive: include_inactive === 'true',
                role_filter
            };
            // Get contact tree for specific department
            const contactTree = await Contact.getContactTree(options);
            // Format based on requested format
            let formattedContacts;
            switch (format) {
                case 'flat':
                    formattedContacts = ContactController.flattenTreeStructure(contactTree);
                    // Apply logo URL to flat structure
                    formattedContacts = formattedContacts.map(contact => ContactController.addLogoUrl(req, contact) || contact);
                    break;
                case 'hierarchical':
                    formattedContacts = ContactController.formatHierarchicalStructure(contactTree, req);
                    break;
                case 'tree':
                default:
                    formattedContacts = ContactController.formatTreeStructure(contactTree, req);
                    break;
            }
            // Get department-specific statistics
            const statistics = await Contact.getEnhancedStatistics(options);
            res.status(200).json({
                status: 'success',
                message: 'Department contact tree retrieved successfully',
                data: {
                    department_id: parseInt(departmentId),
                    contacts: formattedContacts,
                    statistics,
                    filters: {
                        search,
                        format,
                        include_inactive: include_inactive === 'true',
                        role_filter
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get contact tree by department (replaces branch)
    static async getContactTreeByBranch(req, res, next) {
        try {
            const { branchId } = req.params;
            const { search = '', format = 'tree', include_inactive = 'false', role_filter = 'all' } = req.query;
            const options = {
                search,
                department_id: branchId,
                include_inactive: include_inactive === 'true',
                role_filter
            };
            // Get contact tree for specific branch
            const contactTree = await Contact.getContactTree(options);
            // Format based on requested format
            let formattedContacts;
            switch (format) {
                case 'flat':
                    formattedContacts = ContactController.flattenTreeStructure(contactTree);
                    // Apply logo URL to flat structure
                    formattedContacts = formattedContacts.map(contact => ContactController.addLogoUrl(req, contact) || contact);
                    break;
                case 'hierarchical':
                    formattedContacts = ContactController.formatHierarchicalStructure(contactTree, req);
                    break;
                case 'tree':
                default:
                    formattedContacts = ContactController.formatTreeStructure(contactTree, req);
                    break;
            }
            // Get branch-specific statistics
            const statistics = await Contact.getEnhancedStatistics(options);
            res.status(200).json({
                status: 'success',
                message: 'Department contact tree retrieved successfully',
                data: {
                    department_id: parseInt(branchId),
                    contacts: formattedContacts,
                    statistics,
                    filters: {
                        search,
                        format,
                        include_inactive: include_inactive === 'true',
                        role_filter
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Get hierarchical organizational structure
    static async getOrganizationalHierarchy(req, res, next) {
        try {
            const { pool } = require('../config/database');
            // Get organizational hierarchy with user counts
            let units = [];
            try {
                const [rows] = await pool.execute(`
          SELECT u.*, 
                 COUNT(DISTINCT u_users.id) as user_count
          FROM units u
          LEFT JOIN users u_users ON u.id = u_users.unit_id AND u_users.is_active = 1
          WHERE u.is_active = 1
          GROUP BY u.id
          ORDER BY u.name
        `);
                units = rows;
            }
            catch (e) {
                if (!(e && (e.code === 'ER_NO_SUCH_TABLE' || /Table '.*\.units' doesn't exist/i.test(e.message)))) {
                    throw e;
                }
                units = [];
            }
            res.status(200).json({
                status: 'success',
                message: 'Organizational hierarchy retrieved successfully',
                data: {
                    units
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Bulk move contacts to different parent
    static async bulkMoveContacts(req, res, next) {
        try {
            const { contactIds, parent_id } = req.body;
            if (!Array.isArray(contactIds) || contactIds.length === 0) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Contact IDs array is required'
                });
            }
            // Validate parent if provided
            if (parent_id) {
                const parentContact = await Contact.findById(parent_id);
                if (!parentContact) {
                    return res.status(400).json({
                        status: 'error',
                        message: 'Parent contact not found'
                    });
                }
            }
            const results = [];
            const errors = [];
            for (const id of contactIds) {
                try {
                    const contact = await Contact.findById(id);
                    if (!contact) {
                        errors.push({ id, error: 'Contact not found' });
                        continue;
                    }
                    // Check for circular reference
                    if (parent_id) {
                        const circularCheck = await ContactController.checkCircularReference(id, parent_id);
                        if (circularCheck.hasCircularReference) {
                            errors.push({ id, error: circularCheck.message });
                            continue;
                        }
                    }
                    // Validate parent-child relationship
                    if (parent_id) {
                        const parentValidation = await ContactController.validateParentChildRelationship(parent_id, contact.unit_id);
                        if (!parentValidation.valid) {
                            errors.push({ id, error: parentValidation.message });
                            continue;
                        }
                    }
                    const updated = await contact.update({ parent_id });
                    if (updated) {
                        results.push({ id, status: 'moved', new_parent_id: parent_id });
                    }
                    else {
                        errors.push({ id, error: 'Failed to move contact' });
                    }
                }
                catch (error) {
                    errors.push({ id, error: error.message });
                }
            }
            res.status(200).json({
                status: 'success',
                message: 'Bulk move operation completed',
                data: {
                    moved: results,
                    errors,
                    summary: {
                        total: contactIds.length,
                        moved: results.length,
                        errors: errors.length
                    }
                }
            });
        }
        catch (error) {
            next(error);
        }
    }
    // Helper method to flatten tree structure
    static flattenTreeStructure(tree, level = 0) {
        const flattened = [];
        const flattenNode = (node) => {
            const nodeData = typeof node.toJSON === 'function' ? node.toJSON() : node;
            flattened.push({
                ...nodeData,
                level,
                has_children: node.children && node.children.length > 0,
                children_count: node.children ? node.children.length : 0,
                hierarchy_path: ContactController.getHierarchyPath(node),
                organizational_path: ContactController.getOrganizationalPath(node)
            });
            if (node.children && node.children.length > 0) {
                node.children.forEach(child => flattenNode(child, level + 1));
            }
        };
        tree.forEach(node => flattenNode(node));
        return flattened;
    }
    // Helper method to format tree structure
    static formatTreeStructure(tree, req = null) {
        return tree.map(node => {
            // Apply logo URL conversion before formatting
            const nodeWithLogo = req ? (ContactController.addLogoUrl(req, node) || (typeof node.toJSON === 'function' ? node.toJSON() : node)) : (typeof node.toJSON === 'function' ? node.toJSON() : node);
            const nodeData = nodeWithLogo || node;
            return {
                ...nodeData,
                display_name: `${node.rank_name || ''} ${node.name}`.trim(),
                display_info: `${node.designation_name || ''} - ${node.service_no || ''}`.trim(),
                organizational_path: ContactController.getOrganizationalPath(node),
                hierarchy_path: ContactController.getHierarchyPath(node),
                has_children: node.children && node.children.length > 0,
                children_count: node.children ? node.children.length : 0,
                children: node.children ? ContactController.formatTreeStructure(node.children, req) : []
            };
        });
    }
    // Helper method to format hierarchical structure
    static formatHierarchicalStructure(tree) {
        return tree.map(node => {
            const nodeData = typeof node.toJSON === 'function' ? node.toJSON() : node;
            return {
                ...nodeData,
                display_name: `${node.rank_name || ''} ${node.name}`.trim(),
                display_info: `${node.designation_name || ''} - ${node.service_no || ''}`.trim(),
                organizational_path: ContactController.getOrganizationalPath(node),
                hierarchy_path: ContactController.getHierarchyPath(node),
                level: 0,
                has_children: node.children && node.children.length > 0,
                children_count: node.children ? node.children.length : 0,
                children: node.children ? ContactController.formatHierarchicalStructure(node.children, 1) : []
            };
        });
    }
    // Helper method to get organizational path
    static getOrganizationalPath(contact) {
        const path = [];
        if (contact.unit_name)
            path.push(contact.unit_name);
        return path.join(' > ');
    }
    // Helper method to get hierarchy path
    static getHierarchyPath(contact) {
        const path = [];
        if (contact.rank_name)
            path.push(contact.rank_name);
        path.push(contact.name);
        if (contact.designation_name)
            path.push(contact.designation_name);
        return path.join(' - ');
    }
    // Helper method to validate parent-child relationship
    static async validateParentChildRelationship(parentId, unitId) {
        try {
            const parent = await Contact.findById(parentId);
            if (!parent) {
                return { valid: false, message: 'Parent contact not found' };
            }
            // Check if parent is in the same organizational structure
            if (parent.unit_id !== unitId) {
                return { valid: false, message: 'Parent must be in the same unit' };
            }
            return { valid: true, message: 'Parent-child relationship is valid' };
        }
        catch (error) {
            return { valid: false, message: 'Error validating parent-child relationship' };
        }
    }
    // Helper method to check for duplicates
    static async checkForDuplicates(serviceNo, email, loginId) {
        try {
            const { pool } = require('../config/database');
            // Check for duplicate service number (only if provided)
            if (serviceNo && serviceNo.trim() !== '') {
                const [serviceNoRows] = await pool.execute('SELECT id FROM users WHERE service_no = ? AND is_active = 1', [serviceNo]);
                if (serviceNoRows.length > 0) {
                    return { exists: true, message: 'Service number already exists' };
                }
            }
            // Check for duplicate email (only if provided)
            if (email && email.trim() !== '') {
                const [emailRows] = await pool.execute('SELECT id FROM users WHERE email = ? AND is_active = 1', [email]);
                if (emailRows.length > 0) {
                    return { exists: true, message: 'Email already exists' };
                }
            }
            // Check for duplicate login_id (only if provided)
            if (loginId && loginId.trim() !== '') {
                const [loginIdRows] = await pool.execute('SELECT id FROM users WHERE login_id = ? AND is_active = 1', [loginId]);
                if (loginIdRows.length > 0) {
                    return { exists: true, message: 'Login ID already exists' };
                }
            }
            return { exists: false, message: 'No duplicates found' };
        }
        catch (error) {
            console.error('Error in checkForDuplicates:', error);
            return { exists: true, message: `Error checking for duplicates: ${error.message}` };
        }
    }
    // Helper method to check for duplicates on update (excluding current contact)
    static async checkForDuplicatesOnUpdate(contactId, serviceNo, email, loginId = null) {
        try {
            const { pool } = require('../config/database');
            // Check for duplicate service number (only if provided)
            if (serviceNo && serviceNo.trim() !== '') {
                const [serviceNoRows] = await pool.execute('SELECT id FROM users WHERE service_no = ? AND is_active = 1 AND id != ?', [serviceNo, contactId]);
                if (serviceNoRows.length > 0) {
                    return { exists: true, message: `Service number '${serviceNo}' already exists for another contact` };
                }
            }
            // Check for duplicate email (only if provided)
            if (email && email.trim() !== '') {
                const [emailRows] = await pool.execute('SELECT id FROM users WHERE email = ? AND is_active = 1 AND id != ?', [email, contactId]);
                if (emailRows.length > 0) {
                    return { exists: true, message: `Email '${email}' already exists for another contact` };
                }
            }
            // Check for duplicate login_id (if provided)
            if (loginId && loginId.trim() !== '') {
                const [loginIdRows] = await pool.execute('SELECT id FROM users WHERE login_id = ? AND is_active = 1 AND id != ?', [loginId, contactId]);
                if (loginIdRows.length > 0) {
                    return { exists: true, message: `Login ID '${loginId}' already exists for another contact` };
                }
            }
            return { exists: false, message: 'No duplicates found' };
        }
        catch (error) {
            // Do not block updates if duplicate check fails; allow update to proceed
            return { exists: false, message: 'Duplicate check skipped' };
        }
    }
    // Helper method to check for circular reference
    static async checkCircularReference(contactId, parentId) {
        try {
            const { pool } = require('../config/database');
            // Check if the potential parent is a descendant of the contact being updated
            const checkQuery = `
        WITH RECURSIVE descendant_tree AS (
          SELECT id, parent_id, 1 as level
          FROM users 
          WHERE id = ?
          AND is_active = 1
          
          UNION ALL
          
          SELECT u.id, u.parent_id, dt.level + 1
          FROM users u
          INNER JOIN descendant_tree dt ON u.parent_id = dt.id
          WHERE u.is_active = 1
          AND dt.level < 10 -- Prevent infinite recursion
        )
        SELECT id FROM descendant_tree WHERE id = ?
      `;
            const [rows] = await pool.execute(checkQuery, [contactId, parentId]);
            if (rows.length > 0) {
                return {
                    hasCircularReference: true,
                    message: 'Cannot set parent as it would create a circular reference'
                };
            }
            return { hasCircularReference: false, message: 'No circular reference detected' };
        }
        catch (error) {
            return { hasCircularReference: true, message: 'Error checking for circular reference' };
        }
    }
    // Helper method to build hierarchy path
    static async buildHierarchyPath(contactId) {
        try {
            const { pool } = require('../config/database');
            // Get the full hierarchy path from root to the contact
            const hierarchyQuery = `
        WITH RECURSIVE hierarchy_path AS (
          SELECT id, parent_id, name, rank_name, designation_name, 0 as level
          FROM users 
          WHERE id = ?
          AND is_active = 1
          
          UNION ALL
          
          SELECT u.id, u.parent_id, u.name, u.rank_name, u.designation_name, hp.level + 1
          FROM users u
          INNER JOIN hierarchy_path hp ON u.id = hp.parent_id
          WHERE u.is_active = 1
          AND hp.level < 10 -- Prevent infinite recursion
        )
        SELECT id, parent_id, name, rank_name, designation_name, level
        FROM hierarchy_path
        ORDER BY level DESC
      `;
            const [rows] = await pool.execute(hierarchyQuery, [contactId]);
            return rows.map(row => ({
                id: row.id,
                name: row.name,
                rank_name: row.rank_name,
                designation_name: row.designation_name,
                level: row.level,
                display_name: `${row.rank_name || ''} ${row.name}`.trim(),
                hierarchy_position: row.level === 0 ? 'current' : 'ancestor'
            }));
        }
        catch (error) {
            throw new Error(`Error building hierarchy path: ${error.message}`);
        }
    }
    // Helper method to validate organizational structure
    static async validateOrganizationalStructure(unitId, departmentId, parentId) {
        try {
            const { pool } = require('../config/database');
            let effectiveUnitId = unitId ?? null;
            if (effectiveUnitId) {
                const [unitRows] = await pool.execute('SELECT id, name FROM units WHERE id = ? AND is_active = 1', [effectiveUnitId]);
                if (unitRows.length === 0) {
                    return { valid: false, message: 'Invalid unit ID or unit is inactive' };
                }
            }
            if (departmentId) {
                const [deptRows] = await pool.execute('SELECT id, name FROM departments WHERE id = ? AND is_active = 1', [departmentId]);
                if (deptRows.length === 0) {
                    return { valid: false, message: 'Invalid department ID or department is inactive' };
                }
                // Departments are no longer linked to units; skip unit consistency checks
            }
            if (parentId) {
                const [parentRows] = await pool.execute('SELECT id, name, unit_id, department_id FROM users WHERE id = ? AND is_active = 1', [parentId]);
                if (parentRows.length === 0) {
                    return { valid: false, message: 'Invalid parent ID or parent is inactive' };
                }
                if (effectiveUnitId && parentRows[0].unit_id !== effectiveUnitId) {
                    return { valid: false, message: 'Parent must belong to the same unit or department context' };
                }
            }
            return { valid: true, message: 'Organizational structure is valid' };
        }
        catch (error) {
            return { valid: false, message: 'Error validating organizational structure' };
        }
    }
    // Change contact password
    static async changePassword(req, res, _next) {
        try {
            const { id } = req.params;
            const { password } = req.body;
            // Validate input
            if (!password || password.trim().length < 6) {
                return res.status(400).json({
                    status: 'error',
                    message: 'Password must be at least 6 characters long'
                });
            }
            // Check if contact exists
            const contact = await Contact.findById(id);
            if (!contact) {
                return res.status(404).json({
                    status: 'error',
                    message: 'Contact not found'
                });
            }
            // Update password
            const result = await Contact.updatePassword(id, password.trim());
            if (result.success) {
                res.status(200).json({
                    status: 'success',
                    message: 'Password changed successfully'
                });
            }
            else {
                res.status(500).json({
                    status: 'error',
                    message: 'Failed to change password'
                });
            }
        }
        catch (error) {
            console.error('Error changing password:', error);
            res.status(500).json({
                status: 'error',
                message: 'Internal server error',
                error: error.message
            });
        }
    }
    static async syncGroupContactMapping(contactId, departmentId, organizationId) {
        try {
            if (!contactId || !organizationId) {
                return;
            }
            const result = await GroupContact.addMappings([{
                    organization_id: organizationId,
                    department_id: departmentId || null,
                    contact_id: contactId
                }]);
            if (!result || (result.inserted === 0 && result.updated === 0)) {
                await GroupContact.forceInsert(organizationId, departmentId || null, contactId);
            }
        }
        catch (error) {
            console.error('❌ Failed to sync group_contact_mappings:', {
                contactId,
                departmentId,
                organizationId,
                error: error.message
            });
        }
    }
}
module.exports = ContactController;
//# sourceMappingURL=contactController.js.map