/** @odoo-module **/
// Fusion LTC Management - Google Address Autocomplete for LTC Facilities
// Copyright 2024-2026 Nexa Systems Inc.
// License OPL-1

import { FormController } from "@web/views/form/form_controller";
import { useService } from "@web/core/utils/hooks";
import { onMounted, onWillUnmount } from "@odoo/owl";
import { patch } from "@web/core/utils/patch";

// Module-scoped state
let googleMapsLoaded = false;
let googleMapsLoading = false;
let googleMapsApiKey = null;
let globalOrm = null;
const autocompleteInstances = new Map();

/**
 * Load Google Maps API dynamically
 */
async function loadGoogleMapsApi(apiKey) {
    if (googleMapsLoaded) {
        return Promise.resolve();
    }

    if (googleMapsLoading) {
        return new Promise((resolve) => {
            const checkLoaded = setInterval(() => {
                if (googleMapsLoaded) {
                    clearInterval(checkLoaded);
                    resolve();
                }
            }, 100);
        });
    }

    googleMapsLoading = true;

    return new Promise((resolve, reject) => {
        // Check if already loaded by another module (e.g. fusion_claims)
        if (window.google && window.google.maps && window.google.maps.places) {
            googleMapsLoaded = true;
            googleMapsLoading = false;
            resolve();
            return;
        }

        // Check if script tag already exists
        const existingScript = document.querySelector('script[src*="maps.googleapis.com/maps/api/js"]');
        if (existingScript) {
            const checkReady = setInterval(() => {
                if (window.google && window.google.maps && window.google.maps.places) {
                    googleMapsLoaded = true;
                    googleMapsLoading = false;
                    clearInterval(checkReady);
                    resolve();
                }
            }, 100);
            return;
        }

        window.initGoogleMapsAutocompleteLTC = () => {
            googleMapsLoaded = true;
            googleMapsLoading = false;
            resolve();
        };

        const script = document.createElement('script');
        script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=initGoogleMapsAutocompleteLTC`;
        script.async = true;
        script.defer = true;
        script.onerror = () => {
            googleMapsLoading = false;
            reject(new Error('Failed to load Google Maps API'));
        };
        document.head.appendChild(script);
    });
}

/**
 * Get API key from Odoo config (uses same key as fusion_claims)
 */
async function getGoogleMapsApiKey(orm) {
    if (googleMapsApiKey) {
        return googleMapsApiKey;
    }

    try {
        const result = await orm.call(
            'ir.config_parameter',
            'get_param',
            ['fusion_claims.google_maps_api_key']
        );
        googleMapsApiKey = result || null;
        return googleMapsApiKey;
    } catch (error) {
        console.warn('[LTC GooglePlaces] Could not fetch Google Maps API key:', error);
        return null;
    }
}

/**
 * Simulate Many2One field selection by finding the widget and triggering its update
 */
async function simulateMany2OneSelection(formEl, fieldName, valueId, displayName) {
    const fieldSelectors = [
        `[name="${fieldName}"]`,
        `.o_field_widget[name="${fieldName}"]`,
        `div[name="${fieldName}"]`,
    ];

    let fieldContainer = null;
    for (const selector of fieldSelectors) {
        fieldContainer = formEl.querySelector(selector);
        if (fieldContainer) break;
    }

    if (!fieldContainer) return false;

    // Try OWL component approach
    let owlComponent = null;
    let el = fieldContainer;
    while (el && !owlComponent) {
        if (el.__owl__) {
            owlComponent = el.__owl__;
            break;
        }
        el = el.parentElement;
    }

    if (owlComponent && owlComponent.component) {
        const component = owlComponent.component;

        if (component.props && component.props.record) {
            try {
                await component.props.record.update({ [fieldName]: valueId });
                return true;
            } catch (_) { /* fallthrough */ }
        }

        if (typeof component.updateValue === 'function') {
            try {
                await component.updateValue(valueId);
                return true;
            } catch (_) { /* fallthrough */ }
        }
    }

    // Fallback: manipulate input directly
    const inputEl = fieldContainer.querySelector('input:not([type="hidden"])');
    if (inputEl) {
        inputEl.focus();
        inputEl.value = '';
        inputEl.value = displayName;

        inputEl.dispatchEvent(new InputEvent('input', {
            bubbles: true,
            cancelable: true,
            data: displayName,
            inputType: 'insertText'
        }));

        await new Promise(resolve => setTimeout(resolve, 250));

        const dropdownSelectors = [
            '.o-autocomplete--dropdown-menu .o-autocomplete--dropdown-item',
            '.o_m2o_dropdown_option',
            '.dropdown-menu .dropdown-item',
            '.o-autocomplete .dropdown-item',
            'ul.ui-autocomplete li',
            '.o_field_many2one_selection li',
        ];

        let found = false;
        for (const selector of dropdownSelectors) {
            const dropdownItems = document.querySelectorAll(selector);
            for (const item of dropdownItems) {
                const itemText = item.textContent.trim();
                if (itemText.includes(displayName) || displayName.includes(itemText)) {
                    item.click();
                    found = true;
                    break;
                }
            }
            if (found) break;
        }

        if (!found) {
            inputEl.dispatchEvent(new KeyboardEvent('keydown', {
                key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true
            }));
            await new Promise(resolve => setTimeout(resolve, 50));
            inputEl.dispatchEvent(new KeyboardEvent('keydown', {
                key: 'Tab', code: 'Tab', keyCode: 9, bubbles: true
            }));
        }

        await new Promise(resolve => setTimeout(resolve, 50));
        inputEl.blur();
        return found;
    }

    return false;
}

/**
 * Setup autocomplete for LTC Facility form.
 * Attaches establishment search on the name field and address search on street.
 */
async function setupFacilityAutocomplete(el, model, orm) {
    globalOrm = orm;

    const apiKey = await getGoogleMapsApiKey(orm);
    if (!apiKey) return;
    try { await loadGoogleMapsApi(apiKey); } catch (e) { return; }

    // --- Name field: establishment autocomplete ---
    const nameSelectors = [
        '.oe_title [name="name"] input',
        'div[name="name"] input',
        '.o_field_widget[name="name"] input',
        '[name="name"] input',
    ];

    let nameInput = null;
    for (const sel of nameSelectors) {
        nameInput = el.querySelector(sel);
        if (nameInput) break;
    }

    if (nameInput && !autocompleteInstances.has('facility_name_' + (nameInput.id || 'default'))) {
        _attachFacilityNameAutocomplete(nameInput, el, model);
    }

    // --- Street field: address autocomplete ---
    const streetSelectors = [
        'div[name="street"] input',
        '.o_field_widget[name="street"] input',
        '[name="street"] input',
    ];

    let streetInput = null;
    for (const sel of streetSelectors) {
        streetInput = el.querySelector(sel);
        if (streetInput) break;
    }

    if (streetInput && !autocompleteInstances.has(streetInput)) {
        _attachFacilityAddressAutocomplete(streetInput, el, model);
    }
}

/**
 * Attach establishment (business) autocomplete on facility name field.
 * Selecting a business fills name, address, phone, email, and website.
 */
function _attachFacilityNameAutocomplete(input, el, model) {
    if (!input || !window.google?.maps?.places) return;

    const instanceKey = 'facility_name_' + (input.id || 'default');
    if (autocompleteInstances.has(instanceKey)) return;

    const autocomplete = new google.maps.places.Autocomplete(input, {
        componentRestrictions: { country: 'ca' },
        types: ['establishment'],
        fields: [
            'place_id', 'name', 'address_components', 'formatted_address',
            'formatted_phone_number', 'international_phone_number', 'website',
        ],
    });

    autocomplete.addListener('place_changed', async () => {
        let place = autocomplete.getPlace();
        if (!place.name && !place.place_id) return;

        if (place.place_id && !place.formatted_phone_number && !place.website) {
            try {
                const service = new google.maps.places.PlacesService(document.createElement('div'));
                const details = await new Promise((resolve, reject) => {
                    service.getDetails(
                        {
                            placeId: place.place_id,
                            fields: ['formatted_phone_number', 'international_phone_number', 'website'],
                        },
                        (result, status) => {
                            if (status === google.maps.places.PlacesServiceStatus.OK) resolve(result);
                            else reject(new Error(status));
                        }
                    );
                });
                if (details.formatted_phone_number) place.formatted_phone_number = details.formatted_phone_number;
                if (details.international_phone_number) place.international_phone_number = details.international_phone_number;
                if (details.website) place.website = details.website;
            } catch (_) { /* ignore */ }
        }

        let streetNumber = '', streetName = '', unitNumber = '';
        let city = '', province = '', postalCode = '', countryCode = '';

        if (place.address_components) {
            for (const c of place.address_components) {
                const t = c.types;
                if (t.includes('street_number')) streetNumber = c.long_name;
                else if (t.includes('route')) streetName = c.long_name;
                else if (t.includes('subpremise')) unitNumber = c.long_name;
                else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
                else if (t.includes('locality')) city = c.long_name;
                else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
                else if (t.includes('administrative_area_level_1')) province = c.short_name;
                else if (t.includes('postal_code')) postalCode = c.long_name;
                else if (t.includes('country')) countryCode = c.short_name;
            }
        }

        const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
        const phone = place.formatted_phone_number || place.international_phone_number || '';

        if (!model?.root) return;
        const record = model.root;

        let countryId = null, stateId = null;
        if (globalOrm && countryCode) {
            try {
                const [countries, states] = await Promise.all([
                    globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
                    province
                        ? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
                        : Promise.resolve([]),
                ]);
                if (countries.length) countryId = countries[0].id;
                if (states.length) stateId = states[0].id;
            } catch (_) { /* ignore */ }
        }

        if (record.resId && globalOrm) {
            try {
                const firstWrite = {};
                if (place.name) firstWrite.name = place.name;
                if (street) firstWrite.street = street;
                if (unitNumber) firstWrite.street2 = unitNumber;
                if (city) firstWrite.city = city;
                if (postalCode) firstWrite.zip = postalCode;
                if (phone) firstWrite.phone = phone;
                if (place.website) firstWrite.website = place.website;
                if (countryId) firstWrite.country_id = countryId;

                await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);

                if (stateId) {
                    await new Promise(r => setTimeout(r, 100));
                    await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
                }

                await record.load();
            } catch (err) {
                console.error('[LTC GooglePlaces] Name autocomplete ORM write failed:', err);
            }
        } else {
            try {
                const textUpdate = {};
                if (place.name) textUpdate.name = place.name;
                if (street) textUpdate.street = street;
                if (unitNumber) textUpdate.street2 = unitNumber;
                if (city) textUpdate.city = city;
                if (postalCode) textUpdate.zip = postalCode;
                if (phone) textUpdate.phone = phone;
                if (place.website) textUpdate.website = place.website;

                await record.update(textUpdate);

                if (countryId && globalOrm) {
                    const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
                    const [countryData, stateData] = await Promise.all([
                        globalOrm.read('res.country', [countryId], ['display_name']),
                        stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
                    ]);

                    await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
                    await new Promise(r => setTimeout(r, 300));

                    if (stateId && stateData.length) {
                        await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
                    }
                }
            } catch (err) {
                console.error('[LTC GooglePlaces] Name autocomplete update failed:', err);
            }
        }
    });

    autocompleteInstances.set(instanceKey, autocomplete);

    input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%232196F3\'%3E%3Cpath d=\'M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z\'/%3E%3C/svg%3E")';
    input.style.backgroundRepeat = 'no-repeat';
    input.style.backgroundPosition = 'right 8px center';
    input.style.backgroundSize = '20px';
    input.style.paddingRight = '35px';
}

/**
 * Attach address autocomplete on facility street field.
 * Fills street, street2, city, state, zip, and country.
 */
function _attachFacilityAddressAutocomplete(input, el, model) {
    if (!input || !window.google?.maps?.places) return;
    if (autocompleteInstances.has(input)) return;

    const autocomplete = new google.maps.places.Autocomplete(input, {
        componentRestrictions: { country: 'ca' },
        types: ['address'],
        fields: ['address_components', 'formatted_address'],
    });

    autocomplete.addListener('place_changed', async () => {
        const place = autocomplete.getPlace();
        if (!place.address_components) return;

        let streetNumber = '', streetName = '', unitNumber = '';
        let city = '', province = '', postalCode = '', countryCode = '';

        for (const c of place.address_components) {
            const t = c.types;
            if (t.includes('street_number')) streetNumber = c.long_name;
            else if (t.includes('route')) streetName = c.long_name;
            else if (t.includes('subpremise')) unitNumber = c.long_name;
            else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
            else if (t.includes('locality')) city = c.long_name;
            else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
            else if (t.includes('administrative_area_level_1')) province = c.short_name;
            else if (t.includes('postal_code')) postalCode = c.long_name;
            else if (t.includes('country')) countryCode = c.short_name;
        }

        const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;

        if (!model?.root) return;
        const record = model.root;

        let countryId = null, stateId = null;
        if (globalOrm && countryCode) {
            try {
                const [countries, states] = await Promise.all([
                    globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
                    province
                        ? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
                        : Promise.resolve([]),
                ]);
                if (countries.length) countryId = countries[0].id;
                if (states.length) stateId = states[0].id;
            } catch (_) { /* ignore */ }
        }

        if (record.resId && globalOrm) {
            try {
                const firstWrite = {};
                if (street) firstWrite.street = street;
                if (unitNumber) firstWrite.street2 = unitNumber;
                if (city) firstWrite.city = city;
                if (postalCode) firstWrite.zip = postalCode;
                if (countryId) firstWrite.country_id = countryId;

                await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);

                if (stateId) {
                    await new Promise(r => setTimeout(r, 100));
                    await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
                }

                await record.load();
            } catch (err) {
                console.error('[LTC GooglePlaces] Address ORM write failed:', err);
            }
        } else {
            try {
                const textUpdate = {};
                if (street) textUpdate.street = street;
                if (unitNumber) textUpdate.street2 = unitNumber;
                if (city) textUpdate.city = city;
                if (postalCode) textUpdate.zip = postalCode;

                await record.update(textUpdate);

                if (countryId && globalOrm) {
                    const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
                    const [countryData, stateData] = await Promise.all([
                        globalOrm.read('res.country', [countryId], ['display_name']),
                        stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
                    ]);

                    await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
                    await new Promise(r => setTimeout(r, 300));

                    if (stateId && stateData.length) {
                        await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
                    }
                }
            } catch (err) {
                console.error('[LTC GooglePlaces] Address autocomplete update failed:', err);
            }
        }

        setTimeout(() => { _reattachFacilityAutocomplete(el, model); }, 400);
    });

    autocompleteInstances.set(input, autocomplete);

    input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%234CAF50\'%3E%3Cpath d=\'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z\'/%3E%3C/svg%3E")';
    input.style.backgroundRepeat = 'no-repeat';
    input.style.backgroundPosition = 'right 8px center';
    input.style.backgroundSize = '20px';
    input.style.paddingRight = '35px';
}

/**
 * Re-attach facility autocomplete after OWL re-renders inputs.
 */
function _reattachFacilityAutocomplete(el, model) {
    const streetSelectors = [
        'div[name="street"] input',
        '.o_field_widget[name="street"] input',
        '[name="street"] input',
    ];
    for (const sel of streetSelectors) {
        const inp = el.querySelector(sel);
        if (inp && !autocompleteInstances.has(inp)) {
            _attachFacilityAddressAutocomplete(inp, el, model);
            break;
        }
    }
}

/**
 * Patch FormController to add Google autocomplete for LTC Facility forms
 */
patch(FormController.prototype, {
    setup() {
        super.setup(...arguments);

        this.orm = useService("orm");

        onMounted(() => {
            // LTC Facility form
            if (this.props.resModel === 'fusion.ltc.facility') {
                setTimeout(() => {
                    if (this.rootRef && this.rootRef.el) {
                        setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
                    }
                }, 800);

                if (this.rootRef && this.rootRef.el) {
                    this._facilityAddrObserver = new MutationObserver((mutations) => {
                        const hasNewInputs = mutations.some(m =>
                            m.addedNodes.length > 0 &&
                            Array.from(m.addedNodes).some(n =>
                                n.nodeType === 1 && (n.tagName === 'INPUT' || n.querySelector?.('input'))
                            )
                        );
                        if (hasNewInputs) {
                            setTimeout(() => {
                                setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
                            }, 300);
                        }
                    });
                    this._facilityAddrObserver.observe(this.rootRef.el, {
                        childList: true,
                        subtree: true,
                    });
                }
            }
        });

        onWillUnmount(() => {
            if (this._facilityAddrObserver) {
                this._facilityAddrObserver.disconnect();
            }
        });
    },
});
