var _useState_SDG = React.useState;
var _useEffect_SDG = React.useEffect;
var _useMemo_SDG = React.useMemo;
var _useRef_SDG = React.useRef;
var _useCallback_SDG = React.useCallback;

/**
 * SpecialistDirectoryPage — Browse local mould specialists, against the LIVE Specialists
 * Search API (specialists-api-reference-pack) via /api/specialists, replacing the old
 * bundled Sydney-Hills-only JSON fixture (197 hand-picked records, permanently stale — see
 * `git log` on the old `services/` directory, now removed). See
 * components/SpecialistsService/SpecialistsService.jsx for the data plane this page drives.
 *
 * PAGE-LEVEL CODE IS `SDG`-NAMESPACED (specialists-api-reference-pack §10: `sp` is taken
 * by SpecialistsService/SpecialistIdentity's file-local helpers; `spg` is this page's).
 *
 * SWR flow, the house pattern WeatherPanel/AirQualityPanel already use, applied to a
 * search result instead of a single reading: on mount, a saved (or freshly detected)
 * location resolves a cache key; a cache hit paints instantly, labelled with its age; a
 * live fetch always follows and seamlessly replaces + re-saves the cache. The
 * `cachePainted`/`networkLanded` guard pair (ported verbatim, see the comments below)
 * exists for exactly this file's first search only — every search after that (a keystroke,
 * a category tap, a new location) is a plain live request with its own loading state; the
 * cache-paint dance is for "show something the instant the page opens", not every filter
 * change.
 *
 * THREE STATES, NEVER TWO (the bug this app already shipped once — see
 * INTEGRATION-GUIDE.md §8/§10): results / genuinely-zero (incl. a 404 with `did_you_mean`,
 * or a 400 ambiguous-suburb with `states`) / request-failed (with a cached-results banner
 * when a cache is available, a plain retry card when it isn't).
 *
 * MEMORY: results live ONLY in this component's state — no module-level cache of the
 * result array anywhere in this file (that's what SpecialistsService's bounded IndexedDB
 * cache is for). Every effect aborts its in-flight fetch and clears its timers on cleanup.
 *
 * Route: /specialists
 */

/** Pure lookup — module scope so the hoisted card can use it too, same reasoning
 *  SpecialistDirectoryPage always used for `_categoryIcon_*`. */
var _categoryIcon_SDG = function (cat) {
    return (AppConstants.SPECIALIST_CATEGORY_ICONS || {})[cat] || 'support_agent';
};

/**
 * _SpecialistCard_SDG — one specialist row in the directory.
 *
 * Module scope, deliberately (ported from the original file's own reasoning, still true):
 * declared inside the page component it takes a new function identity every render, and
 * React tears down and rebuilds every card in the list on each keystroke rather than
 * updating them in place.
 */
var _SpecialistCard_SDG = function (props) {
    var navigate = ReactRouterDOM.useNavigate();
    var s = props.specialist;

    var categories = s.categories || [];
    var icon = _categoryIcon_SDG(categories[0] || '');
    var loc = s.location || {};
    var approx = loc.geocode_precision === 'postcode';
    var mouldDetect = s.mould_detect || {};
    var badgeInfo = SpecialistVetting.describe(mouldDetect);
    var ratings = s.ratings || {};
    var hasRating = typeof ratings.google_rating === 'number';
    var hasDistance = typeof s.distance_km === 'number';

    return (
        <div className="bio-bg bio-bg-20 rounded-2xl p-4 shadow-soft border border-stone-100/50 flex flex-col gap-3 group hover:bg-forest transition-all duration-300">
            <div className="flex gap-4">
                {/* Category icon circle */}
                <div className="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center shrink-0 group-hover:bg-primary/20 transition-colors">
                    <span
                        className="material-symbols-outlined text-primary text-2xl"
                        style={{ fontVariationSettings: "'FILL' 1" }}
                    >{icon}</span>
                </div>
                <div className="flex flex-col flex-1 min-w-0">
                    <div className="min-w-0">
                        <h4 className="font-extrabold text-sm text-forest leading-tight truncate group-hover:text-white transition-colors">{s.name}</h4>
                        <p className="text-primary text-xs font-bold mt-0.5 truncate">{categories.join(', ')}</p>
                        {/* Own row, never beside the name: the badge text is long, and an
                            unshrinkable badge next to a min-w-0 truncate name collapses the
                            name to nothing (caught in design review, 2026-08-08). */}
                        {badgeInfo && <div className="mt-1.5"><SpecialistVetting.Badge mouldDetect={mouldDetect} /></div>}
                    </div>
                    <div className="flex items-center gap-3 mt-2 flex-wrap">
                        {hasDistance && (
                            <div className="flex items-center gap-1 text-forest">
                                <span className="material-symbols-outlined text-sm" style={{ fontVariationSettings: "'FILL' 1" }}>my_location</span>
                                <span className="text-xs font-bold">{s.distance_km.toFixed(1)} km away</span>
                            </div>
                        )}
                        {hasRating && (
                            <div className="flex items-center gap-1 text-terracotta">
                                <span className="material-symbols-outlined text-sm" style={{ fontVariationSettings: "'FILL' 1" }}>star</span>
                                <span className="text-xs font-bold">{ratings.google_rating.toFixed(1)}</span>
                                {typeof ratings.google_review_count === 'number' && (
                                    <span className="text-forest text-[10px]">({ratings.google_review_count})</span>
                                )}
                            </div>
                        )}
                        {loc.suburb && (
                            <div className="flex items-center gap-1 text-muted group-hover:text-accent/60 transition-colors">
                                <span className="material-symbols-outlined text-sm">location_on</span>
                                <span className="text-xs">{loc.suburb}{approx ? ' (approx.)' : ''}</span>
                            </div>
                        )}
                        {s.coverage === 'service_area' && (
                            <span className="text-[10px] font-bold text-forest/60 uppercase tracking-wide group-hover:text-accent/60">Services this area</span>
                        )}
                    </div>
                </div>
            </div>
            <button
                onClick={function () { navigate('/specialists/' + s.id, { state: { specialist: s } }); }}
                className="w-full bg-forest text-white py-2.5 rounded-xl font-bold text-sm btn-nature hover:bg-primary transition-colors group-hover:bg-primary"
            >
                View Profile
            </button>
        </div>
    );
};

/** The cache key contract from SpecialistsService's header: `resolved.region_slug` when
 *  the response carries one, else `q:<normalised q>`, else `geo:<lat4>,<lon4>`, plus
 *  `|cat:<category>` when a category filter is active. */
var _spgCacheKey = function (resolved, q, coords, category) {
    var base;
    if (resolved && resolved.region_slug) {
        base = resolved.region_slug;
    } else if (q) {
        base = 'q:' + q.trim().toLowerCase();
    } else if (coords) {
        base = 'geo:' + coords.lat.toFixed(4) + ',' + coords.lon.toFixed(4);
    } else {
        base = 'unknown';
    }
    if (category) { base += '|cat:' + category; }
    return base;
};

var _spgFreshnessLabel = function (savedAt) {
    var hours = Math.round((Date.now() - savedAt) / 3600000);
    if (hours < 1) return 'Saved results · updated moments ago';
    if (hours === 1) return 'Saved results · updated 1h ago';
    return 'Saved results · updated ' + hours + 'h ago';
};

var SpecialistDirectoryPage = function () {
    var navigate = ReactRouterDOM.useNavigate();

    var searchState = _useState_SDG('');
    var search = searchState[0];
    var setSearch = searchState[1];

    var debouncedState = _useState_SDG('');
    var debouncedSearch = debouncedState[0];
    var setDebouncedSearch = debouncedState[1];

    var categoryState = _useState_SDG(''); // '' = All
    var activeCategory = categoryState[0];
    var setActiveCategory = categoryState[1];

    // { lat, lon } once resolved (saved location, live detect, or the Sydney fallback).
    var coordsState = _useState_SDG(null);
    var coords = coordsState[0];
    var setCoords = coordsState[1];

    // Whether we've finished deciding how LocationPicker should render (saved vs. detect)
    // — LocationPicker only mounts once this is true, so its own initial-value props are
    // never stale (see LocationPicker.jsx's `initialLocation`/`skipAutoDetect` doc).
    var locationReadyState = _useState_SDG(false);
    var locationReady = locationReadyState[0];
    var setLocationReady = locationReadyState[1];

    var skipAutoDetectState = _useState_SDG(false);
    var skipAutoDetect = skipAutoDetectState[0];
    var setSkipAutoDetect = skipAutoDetectState[1];

    var initialLocationLabelState = _useState_SDG('');
    var initialLocationLabel = initialLocationLabelState[0];
    var setInitialLocationLabel = initialLocationLabelState[1];

    var localAreaState = _useState_SDG('');
    var localArea = localAreaState[0];
    var setLocalArea = localAreaState[1];

    // Results live ONLY here — component state, never a module-level variable (see this
    // file's MEMORY note above).
    var resultsState = _useState_SDG({ specialists: [], count: 0, resolved: null });
    var results = resultsState[0];
    var setResults = resultsState[1];

    var statusState = _useState_SDG('loading'); // 'loading' | 'ready' | 'zero' | 'error'
    var status = statusState[0];
    var setStatus = statusState[1];

    // { savedAt } when the list currently on screen came from the results cache.
    var cacheInfoState = _useState_SDG(null);
    var cacheInfo = cacheInfoState[0];
    var setCacheInfo = cacheInfoState[1];

    // { kind: 'notfound' | 'ambiguous' | 'failed', reason, didYouMean, states }
    var errorInfoState = _useState_SDG(null);
    var errorInfo = errorInfoState[0];
    var setErrorInfo = errorInfoState[1];

    var retryTickState = _useState_SDG(0);
    var retryTick = retryTickState[0];
    var setRetryTick = retryTickState[1];

    var controllerRef = _useRef_SDG(null);
    var debounceTimerRef = _useRef_SDG(null);
    var firstRunRef = _useRef_SDG(true);

    // ── Debounce the search box (400ms) ─────────────────────────────────────────────
    _useEffect_SDG(function () {
        if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); }
        debounceTimerRef.current = setTimeout(function () {
            setDebouncedSearch(search);
        }, 400);
        return function () {
            if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); }
        };
    }, [search]);

    // ── Resolve a starting location on mount ────────────────────────────────────────
    // Saved location (< 7 days old) wins: painted instantly, LocationPicker never prompts.
    // Otherwise LocationPicker auto-detects as normal, with a budget after which the
    // Sydney fallback is used so a denied/slow prompt doesn't leave the page stuck.
    _useEffect_SDG(function () {
        var cancelled = false;
        var budgetTimer = null;

        SpecialistsService.getSavedLocation().then(function (loc) {
            if (cancelled) return;
            if (loc) {
                setCoords({ lat: loc.lat, lon: loc.lon });
                setInitialLocationLabel(loc.displayName || '');
                setLocalArea(loc.displayName || '');
                setSkipAutoDetect(true);
                setLocationReady(true);
                return;
            }
            setSkipAutoDetect(false);
            setLocationReady(true);
            var fallback = (window.AppConstants && AppConstants.FALLBACK_COORDS) ||
                { lat: -33.8688, lon: 151.2093, name: 'Sydney' };
            budgetTimer = setTimeout(function () {
                if (cancelled) return;
                setCoords(function (prev) { return prev || { lat: fallback.lat, lon: fallback.lon }; });
            }, 6000);
        });

        return function () {
            cancelled = true;
            if (budgetTimer) { clearTimeout(budgetTimer); }
        };
    }, []);

    var handleCoordsChange = _useCallback_SDG(function (c) {
        setCoords({ lat: c.lat, lon: c.lon });
        if (c.displayName) { SpecialistsService.saveLocation(c.lat, c.lon, c.displayName); }
    }, []);

    // ── The search itself ───────────────────────────────────────────────────────────
    _useEffect_SDG(function () {
        if (!coords) return; // still resolving where the user is

        if (controllerRef.current) { controllerRef.current.abort(); }
        var controller = new AbortController();
        controllerRef.current = controller;
        var cancelled = false;

        var isFirstRun = firstRunRef.current;
        firstRunRef.current = false;

        var qParam = debouncedSearch.trim() || null;
        var categoryParam = activeCategory || null;
        var provisionalKey = _spgCacheKey(null, qParam, coords, categoryParam);

        setErrorInfo(null);
        setStatus('loading');

        // GUARD 1 (ported from AirQualityPanel/WeatherPanel): a background refresh
        // failure must never regress a reading that is already painted from cache.
        var cachePainted = false;
        // GUARD 2 (ported): a late cache read must never overpaint fresher data that
        // already landed from the network.
        var networkLanded = false;

        if (isFirstRun) {
            SpecialistsService.load(provisionalKey).then(function (entry) {
                if (cancelled || !entry || networkLanded) return;
                var raw = entry.raw;
                if (raw && Array.isArray(raw.specialists)) {
                    cachePainted = true;
                    setResults(raw);
                    setCacheInfo({ savedAt: entry.savedAt });
                    setStatus(raw.specialists.length > 0 ? 'ready' : 'zero');
                }
            })['catch'](function () { /* a broken cache read must never block the live path */ });
        }

        var query = { q: qParam, category: categoryParam };
        if (!qParam) { query.lat = coords.lat; query.lon = coords.lon; }

        SpecialistsService.search(query, { signal: controller.signal }).then(function (res) {
            if (cancelled) return;

            if (res && res.error) {
                if (res.reason === 'aborted') return; // superseded by a newer search

                if (res.status === 400 || res.status === 404) {
                    // A documented, recoverable "no match" — folded into the genuinely-zero
                    // state (INTEGRATION-GUIDE.md §10: "Show suggestions, don't dead-end").
                    networkLanded = true;
                    setResults({ specialists: [], count: 0, resolved: null });
                    setCacheInfo(null);
                    setErrorInfo({
                        kind: res.status === 400 ? 'ambiguous' : 'notfound',
                        reason: res.reason, didYouMean: res.didYouMean, states: res.states,
                    });
                    setStatus('zero');
                    return;
                }

                // A real failure: network down, 429, 502, 503, unconfigured, etc.
                networkLanded = true;
                setErrorInfo({ kind: 'failed', reason: res.reason });
                if (!cachePainted) { setStatus('error'); }
                // else: leave `status` as the cache already set it (results stay on
                // screen) — the render layer shows the "couldn't reach the directory"
                // banner alongside them, per the pack's own register for this.
                return;
            }

            networkLanded = true;
            setResults(res);
            setCacheInfo(null);
            setStatus((res.specialists || []).length > 0 ? 'ready' : 'zero');

            AnalyticsService.findProfessionalViewed({
                search_query: qParam || '',
                category_filter: categoryParam || 'All',
                result_count: res.count,
            });

            var finalKey = _spgCacheKey(res.resolved, qParam, coords, categoryParam);
            SpecialistsService.save(finalKey, res);
        });

        return function () {
            cancelled = true;
            controller.abort();
        };
    }, [coords, debouncedSearch, activeCategory, retryTick]);

    var clearFilters = function () {
        setSearch('');
        setDebouncedSearch('');
        setActiveCategory('');
    };

    var retry = function () { setRetryTick(function (n) { return n + 1; }); };

    var categoryOptions = _useMemo_SDG(function () {
        return [''].concat(AppConstants.SPECIALIST_CATEGORIES || []);
    }, []);

    var showSpinner = status === 'loading' && results.specialists.length === 0;
    var showFailureBanner = errorInfo && errorInfo.kind === 'failed' && results.specialists.length > 0;
    var showFullError = errorInfo && errorInfo.kind === 'failed' && results.specialists.length === 0 && status === 'error';
    var showZero = status === 'zero' && !showFullError;
    var showList = results.specialists.length > 0;

    return (
        <Layout>
            <main className="flex-1 overflow-y-auto overflow-x-hidden pb-36">
                <PageHeader title="Specialists" showBack={true} showMenu={true} />
                <div className="px-6">

                    {/* Location Picker — only mounted once we know whether to show a saved
                        location instantly or auto-detect (see the effect above). */}
                    {locationReady && (
                        <LocationPicker
                            initialLocation={initialLocationLabel}
                            skipAutoDetect={skipAutoDetect}
                            onLocationChange={setLocalArea}
                            onCoordsChange={handleCoordsChange}
                        />
                    )}

                    {/* Search */}
                    <div className="mt-4 mb-3">
                        <label className="flex h-12 w-full">
                            <div className="flex w-full items-stretch rounded-xl shadow-soft">
                                <div className="text-sage flex items-center justify-center pl-4 bg-surface rounded-l-xl">
                                    <span className="material-symbols-outlined">search</span>
                                </div>
                                <input
                                    className="flex w-full min-w-0 flex-1 rounded-r-xl bg-surface text-forest font-medium placeholder:text-sage px-4 text-sm border-none focus:outline-none focus:ring-2 focus:ring-primary"
                                    placeholder="Search suburb, postcode or specialist..."
                                    value={search}
                                    onChange={function (e) { setSearch(e.target.value); }}
                                />
                            </div>
                        </label>
                    </div>

                    {/* Category Filters — the 12 live API categories */}
                    <div className="flex gap-2 pb-4 overflow-x-auto no-scrollbar">
                        {categoryOptions.map(function (cat) {
                            var isActive = cat === activeCategory;
                            var label = cat || 'All';
                            return (
                                <button
                                    key={label}
                                    onClick={function () { setActiveCategory(cat); }}
                                    className={
                                        'flex h-8 shrink-0 items-center justify-center gap-1.5 rounded-full px-4 text-xs font-bold btn-nature transition-all ' +
                                        (isActive
                                            ? 'bg-forest text-white shadow-soft'
                                            : 'bg-surface border border-stone-200 text-forest/70 hover:border-primary/50')
                                    }
                                >
                                    {cat !== '' && (
                                        <span className={'material-symbols-outlined text-sm ' + (isActive ? 'text-white' : 'text-forest/50')}>{_categoryIcon_SDG(cat)}</span>
                                    )}
                                    <span>{label}</span>
                                </button>
                            );
                        })}
                    </div>

                    {/* Results Header */}
                    <div className="flex items-center justify-between mb-3">
                        <div>
                            <h3 className="text-forest font-extrabold text-base">Local Service Providers</h3>
                            {results && results.resolved && results.resolved.region_name && (
                                <p className="text-[11px] font-bold text-forest/70 mt-0.5">{results.resolved.region_name}{results.resolved.state ? ' \u00b7 ' + results.resolved.state : ''}</p>
                            )}
                            {localArea && (
                                <p className="text-xs font-medium text-forest flex items-center gap-1 mt-0.5">
                                    <span className="material-symbols-outlined text-[12px]">location_on</span>
                                    {localArea}
                                </p>
                            )}
                            {cacheInfo && (
                                <p className="text-[10px] font-bold text-forest/70 mt-0.5">{_spgFreshnessLabel(cacheInfo.savedAt)}</p>
                            )}
                        </div>
                        {showList && <span className="text-forest text-xs font-bold">{results.count} Found</span>}
                    </div>

                    {/* (a) Loading — first-ever paint, nothing on screen yet */}
                    {showSpinner && (
                        <div className="flex items-center justify-center py-16">
                            <div className="w-10 h-10 rounded-full border-4 border-forest/20 border-t-forest animate-spin"></div>
                        </div>
                    )}

                    {/* (c) Request failed, WITH cached results — WeatherPanel's empty-state
                        register: friendly, no red alarm, cached list stays visible. */}
                    {showFailureBanner && (
                        <div className="mb-3 bg-background-light rounded-xl p-3 flex items-center gap-3 border border-stone-200">
                            <span className="material-symbols-outlined text-sage text-xl">cloud_off</span>
                            <p className="text-[11px] text-forest/70 leading-snug flex-1">Showing saved results — we couldn't reach the directory just now.</p>
                            <button onClick={retry} className="text-xs font-bold text-primary shrink-0">Try again</button>
                        </div>
                    )}

                    {/* (c) Request failed, no cache to fall back on */}
                    {showFullError && (
                        <div className="text-center py-12">
                            <span className="material-symbols-outlined text-4xl text-sage mb-2">cloud_off</span>
                            <p className="text-sm font-bold text-forest">The specialist directory isn't available right now</p>
                            <p className="text-xs text-forest/70 mt-1 mb-4">Check your connection and try again.</p>
                            <button
                                onClick={retry}
                                className="bg-primary text-white font-extrabold px-6 py-2.5 rounded-xl shadow-clay btn-nature hover:brightness-110 transition-all text-sm inline-flex items-center gap-2"
                            >
                                <span className="material-symbols-outlined text-base">refresh</span>
                                Try again
                            </button>
                        </div>
                    )}

                    {/* (b) Genuinely zero — incl. a resolvable-but-empty search, a 404
                        with did_you_mean, or a 400 ambiguous-suburb with states. */}
                    {showZero && (
                        <div className="text-center py-12">
                            <span className="material-symbols-outlined text-4xl text-forest mb-2">search_off</span>
                            {errorInfo && errorInfo.kind === 'ambiguous' ? (
                                <React.Fragment>
                                    <p className="text-sm font-bold text-forest">Which state did you mean?</p>
                                    <p className="text-xs text-forest/70 mt-1 mb-3">{errorInfo.reason}</p>
                                    <div className="flex gap-2 flex-wrap justify-center">
                                        {(errorInfo.states || []).map(function (st) {
                                            return (
                                                <button
                                                    key={st}
                                                    onClick={function () { setSearch(search.trim() + ' ' + st); setDebouncedSearch(search.trim() + ' ' + st); }}
                                                    className="px-3 py-1.5 rounded-full bg-forest/10 text-forest text-xs font-bold border border-forest/20 hover:bg-forest hover:text-white transition-colors"
                                                >{st}</button>
                                            );
                                        })}
                                    </div>
                                </React.Fragment>
                            ) : errorInfo && errorInfo.kind === 'notfound' && errorInfo.didYouMean && errorInfo.didYouMean.length > 0 ? (
                                <React.Fragment>
                                    <p className="text-sm font-bold text-forest">No specialists found for "{search}"</p>
                                    <p className="text-xs text-forest/70 mt-1 mb-3">Did you mean:</p>
                                    <div className="flex gap-2 flex-wrap justify-center">
                                        {errorInfo.didYouMean.map(function (suggestion) {
                                            return (
                                                <button
                                                    key={suggestion}
                                                    onClick={function () { setSearch(suggestion); setDebouncedSearch(suggestion); }}
                                                    className="px-3 py-1.5 rounded-full bg-forest/10 text-forest text-xs font-bold border border-forest/20 hover:bg-forest hover:text-white transition-colors"
                                                >{suggestion}</button>
                                            );
                                        })}
                                    </div>
                                </React.Fragment>
                            ) : (
                                <React.Fragment>
                                    <p className="text-sm font-bold text-forest">
                                        No specialists match{results.resolved && results.resolved.region_name ? ' in ' + results.resolved.region_name : ''}
                                    </p>
                                    <p className="text-xs text-forest/70 mt-1 mb-3">Try widening your search or clearing your filters</p>
                                    {(search || activeCategory) && (
                                        <button onClick={clearFilters} className="text-xs font-bold text-primary underline">Clear filters</button>
                                    )}
                                </React.Fragment>
                            )}
                        </div>
                    )}

                    {/* (a) Results */}
                    {showList && (
                        <div className="space-y-3 pb-4">
                            {results.specialists.map(function (s) {
                                // Server-sorted order preserved — never re-sorted client-side
                                // (INTEGRATION-GUIDE.md §4: re-sorting silently destroys the
                                // vetting-first ranking, which is the commercial product).
                                return <_SpecialistCard_SDG key={s.id} specialist={s} />;
                            })}
                        </div>
                    )}
                </div>
            </main>
        </Layout>
    );
};

window.SpecialistDirectoryPage = SpecialistDirectoryPage;
