-- =============================================================================
-- Migration: 20260420000005_search_global_rpc
-- Busca global do topbar — RPC única que retorna resultados agrupados por
-- entidade (pacientes, agendamentos, documentos, serviços).
--
-- Segurança:
--   • SECURITY INVOKER → respeita RLS do chamador (terapeuta vê só os dele,
--     clínica vê do tenant, saas_admin vê global). Sem reinvenção de permissão.
--   • GRANT apenas para `authenticated` (paciente anônimo não tem busca global).
--
-- Índices trigram:
--   • patients(nome_completo, email_principal, cpf)
--   • services(name)
--   • agenda_eventos(titulo, titulo_custom)
--   • documents(nome_original) — já existe em 06_indexes/indexes.sql (skip)
-- =============================================================================

-- -----------------------------------------------------------------------------
-- Índices trigram (GIN) pra ILIKE/similarity performarem
-- pg_trgm instalado em schema `extensions`; ops class vive em `public`.
-- -----------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_patients_nome_trgm
    ON public.patients USING gin (nome_completo public.gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_patients_email_trgm
    ON public.patients USING gin (email_principal public.gin_trgm_ops)
    WHERE email_principal IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_patients_cpf_trgm
    ON public.patients USING gin (cpf public.gin_trgm_ops)
    WHERE cpf IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_services_name_trgm
    ON public.services USING gin (name public.gin_trgm_ops);

CREATE INDEX IF NOT EXISTS idx_agenda_eventos_titulo_trgm
    ON public.agenda_eventos USING gin (titulo public.gin_trgm_ops)
    WHERE titulo IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_agenda_eventos_titulo_custom_trgm
    ON public.agenda_eventos USING gin (titulo_custom public.gin_trgm_ops)
    WHERE titulo_custom IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_patient_intake_requests_nome_trgm
    ON public.patient_intake_requests USING gin (nome_completo public.gin_trgm_ops)
    WHERE status = 'new';

-- -----------------------------------------------------------------------------
-- RPC principal
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.search_global(
    p_q     text,
    p_scope text[] DEFAULT NULL,
    p_limit int    DEFAULT 8
)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY INVOKER
STABLE
SET search_path = public, pg_temp
AS $$
DECLARE
    v_q            text;
    v_pattern      text;
    v_limit        int;
    v_patients     jsonb := '[]'::jsonb;
    v_appointments jsonb := '[]'::jsonb;
    v_documents    jsonb := '[]'::jsonb;
    v_services     jsonb := '[]'::jsonb;
    v_intakes      jsonb := '[]'::jsonb;
BEGIN
    -- Sanitize + length guards
    v_q := nullif(btrim(coalesce(p_q, '')), '');
    IF v_q IS NULL OR length(v_q) < 2 THEN
        RETURN jsonb_build_object(
            'patients',     '[]'::jsonb,
            'appointments', '[]'::jsonb,
            'documents',    '[]'::jsonb,
            'services',     '[]'::jsonb,
            'intakes',      '[]'::jsonb
        );
    END IF;
    v_q := left(v_q, 80);
    v_pattern := '%' || v_q || '%';
    v_limit := GREATEST(1, LEAST(coalesce(p_limit, 8), 20));

    -- ─────────────────────────────────────────────────────────────────────
    -- Pacientes
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'patients' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                p.id,
                p.nome_completo,
                p.email_principal,
                p.telefone,
                p.avatar_url,
                GREATEST(
                    similarity(coalesce(p.nome_completo,   ''), v_q),
                    similarity(coalesce(p.email_principal, ''), v_q) * 0.7,
                    similarity(coalesce(p.telefone,        ''), v_q) * 0.5,
                    similarity(coalesce(p.cpf,             ''), v_q) * 0.6
                ) AS score
            FROM public.patients p
            WHERE p.nome_completo   ILIKE v_pattern
               OR p.email_principal ILIKE v_pattern
               OR p.telefone        ILIKE v_pattern
               OR p.cpf             ILIKE v_pattern
            ORDER BY score DESC, p.nome_completo ASC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',         id,
            'label',      nome_completo,
            'sublabel',   coalesce(nullif(email_principal, ''), nullif(telefone, ''), ''),
            'avatar_url', avatar_url,
            'deeplink',   '/therapist/patients/cadastro/' || id::text,
            'score',      round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_patients
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Agendamentos (com nome do paciente via join)
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'appointments' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                e.id,
                coalesce(nullif(e.titulo_custom, ''), nullif(e.titulo, ''), 'Sessão') AS label,
                e.inicio_em,
                pat.nome_completo AS patient_name,
                GREATEST(
                    similarity(coalesce(e.titulo,        ''), v_q),
                    similarity(coalesce(e.titulo_custom, ''), v_q),
                    similarity(coalesce(pat.nome_completo, ''), v_q) * 0.9
                ) AS score
            FROM public.agenda_eventos e
            LEFT JOIN public.patients pat ON pat.id = e.patient_id
            WHERE e.titulo          ILIKE v_pattern
               OR e.titulo_custom   ILIKE v_pattern
               OR pat.nome_completo ILIKE v_pattern
            ORDER BY score DESC, e.inicio_em DESC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    label,
            'sublabel', trim(both ' · ' from
                            coalesce(patient_name, '') || ' · '
                            || to_char(inicio_em, 'DD/MM/YYYY HH24:MI')),
            'deeplink', '/therapist/agenda?event=' || id::text,
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_appointments
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Documentos
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'documents' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                d.id,
                d.patient_id,
                d.nome_original,
                d.tipo_documento,
                pat.nome_completo AS patient_name,
                GREATEST(
                    similarity(coalesce(d.nome_original, ''), v_q),
                    similarity(coalesce(d.descricao,     ''), v_q) * 0.7
                ) AS score
            FROM public.documents d
            LEFT JOIN public.patients pat ON pat.id = d.patient_id
            WHERE d.nome_original ILIKE v_pattern
               OR d.descricao     ILIKE v_pattern
            ORDER BY score DESC, d.nome_original ASC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    nome_original,
            'sublabel', trim(both ' · ' from
                            coalesce(patient_name, '') || ' · '
                            || coalesce(tipo_documento, '')),
            'deeplink', '/therapist/patients/' || patient_id::text || '/documents',
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_documents
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Serviços (ativos)
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'services' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                s.id,
                s.name,
                s.price,
                s.duration_min,
                GREATEST(
                    similarity(coalesce(s.name,        ''), v_q),
                    similarity(coalesce(s.description, ''), v_q) * 0.7
                ) AS score
            FROM public.services s
            WHERE s.active IS TRUE
              AND (s.name        ILIKE v_pattern
                OR s.description ILIKE v_pattern)
            ORDER BY score DESC, s.name ASC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    name,
            'sublabel', trim(both ' · ' from
                            'R$ ' || to_char(price, 'FM999G999G990D00') || ' · '
                            || coalesce(duration_min::text || ' min', '')),
            'deeplink', '/configuracoes/precificacao',
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_services
        FROM ranked;
    END IF;

    -- ─────────────────────────────────────────────────────────────────────
    -- Intakes pendentes (patient_intake_requests com status='new')
    -- ─────────────────────────────────────────────────────────────────────
    IF p_scope IS NULL OR 'intakes' = ANY(p_scope) THEN
        WITH ranked AS (
            SELECT
                r.id,
                r.nome_completo,
                r.email_principal,
                r.telefone,
                r.created_at,
                GREATEST(
                    similarity(coalesce(r.nome_completo,   ''), v_q),
                    similarity(coalesce(r.email_principal, ''), v_q) * 0.7,
                    similarity(coalesce(r.telefone,        ''), v_q) * 0.5
                ) AS score
            FROM public.patient_intake_requests r
            WHERE r.status = 'new'
              AND (r.nome_completo   ILIKE v_pattern
                OR r.email_principal ILIKE v_pattern
                OR r.telefone        ILIKE v_pattern)
            ORDER BY score DESC, r.created_at DESC
            LIMIT v_limit
        )
        SELECT coalesce(jsonb_agg(jsonb_build_object(
            'id',       id,
            'label',    coalesce(nullif(trim(nome_completo), ''), '(sem nome)'),
            'sublabel', trim(both ' · ' from
                            coalesce(nullif(email_principal, ''), nullif(telefone, ''), '') || ' · '
                            || 'recebido ' || to_char(created_at, 'DD/MM/YYYY')),
            'deeplink', '/therapist/patients/cadastro/recebidos?id=' || id::text,
            'score',    round(score::numeric, 3)
        )), '[]'::jsonb) INTO v_intakes
        FROM ranked;
    END IF;

    RETURN jsonb_build_object(
        'patients',     v_patients,
        'appointments', v_appointments,
        'documents',    v_documents,
        'services',     v_services,
        'intakes',      v_intakes
    );
END;
$$;

REVOKE EXECUTE ON FUNCTION public.search_global(text, text[], int) FROM PUBLIC, anon;
GRANT  EXECUTE ON FUNCTION public.search_global(text, text[], int) TO authenticated;

COMMENT ON FUNCTION public.search_global(text, text[], int) IS
'Busca global do topbar — retorna jsonb agrupado por entidade. SECURITY INVOKER (RLS do chamador aplica).';
